Creating my personal website has been a challenging, educational, and ultimately rewarding experience. What began as a straightforward goal, documenting my learning path in cybersecurity, evolved into a multi-layered project involving hardware assembly, system administration, web development, network configuration, and hands-on troubleshooting.

This entry details every step of how zero-day.is came to life, including both technical decisions and real-world obstacles. I am sharing this not only to document my journey but also to give back to anyone looking to build something similar.

Laying the Foundation: Choosing the Tools

The first crucial decision was to build something that fully reflects my mindset as an aspiring cybersecurity specialist. I wanted complete control over my environment, a deep understanding of every layer, and independence from large hosting providers.

I chose the Raspberry Pi 5 with 8GB RAM as the foundation. It's a small yet powerful device, capable of hosting a full web application, while remaining energy efficient and quiet enough to run at home 24/7. This choice aligned perfectly with my goal to learn system administration hands-on.

After comparing several blogging platforms, I decided on Ghost. It has a clean, minimalist interface that aligns with the seriousness of cybersecurity writing, it's open-source, giving me the freedom to customize themes and behavior as needed, and the platform is fast and optimized for performance, which is critical even on limited hardware.

Choosing self-hosted Ghost over managed platforms meant I would be fully responsible for every aspect of deployment and security, exactly what I wanted.

Setting Up the Raspberry Pi

When my Raspberry Pi arrived, I began with the basics: flashing the latest Raspberry Pi OS onto a microSD card using a USB stick and Raspberry Pi Imager, enabling SSH access by placing an empty ssh file into the boot partition so I could manage the Pi remotely from my main workstation, and connecting the Pi to my network via Ethernet to ensure maximum stability, knowing that even minor network issues could disrupt availability.

From there, I could control everything via terminal on my Windows PC. This workflow immediately began to simulate a real-world server management experience.

Additionally, I set a static IP for the Pi within my router settings to avoid issues with dynamic internal IP allocation. This step is essential for reliable port forwarding and internal management.

Preparing the Web Server Environment

Next, I prepared the Raspberry Pi for running production-grade web services. My checklist included installing Node.js and npm, since they're core dependencies for Ghost, and setting up MySQL to serve as the backend database.

Database configuration involved creating a dedicated database (ghostdb), setting up a new MySQL user (ghostuser) with only the necessary permissions (SELECT, INSERT, UPDATE, DELETE), and securing the MySQL installation by disabling remote root access and removing default test databases.

By following these best practices, I ensured the database would be reliable and secure enough to serve as the backbone of my blog.

Installing and Configuring Ghost

With the environment prepared, I proceeded to install Ghost: installing Ghost CLI, which greatly simplifies installation and management tasks, then initializing Ghost in /var/www/ghost/, choosing a clean directory to keep the setup organized.

Key configuration points were setting the URL to the future production domain (zero-day.is), linking Ghost to the ghostdb database, configuring Ghost to listen on 0.0.0.0 to enable external access, and adjusting the Raspberry Pi firewall to open port 2368, Ghost's default port.

After installation, I tested the local setup by accessing the Pi's IP and port, confirming that Ghost was running correctly.

To enhance the visual identity of my blog, I selected the Solo theme. It is modern, professional, and perfectly suited for the type of content I planned to publish. I liked its clean lines and simplicity, which places the focus squarely on the written word.

Moving to the Internet: Domain and DNS Configuration

The next milestone was making my site globally accessible.

Because my home IP is dynamically assigned by my ISP, I integrated DuckDNS, a free dynamic DNS service: I created an account and registered a subdomain, generated my personal DuckDNS token, and wrote an update script for the Raspberry Pi, configured to run automatically via cronjob every five minutes. This ensured that my domain would always resolve to my current home IP, even after router reboots or ISP-assigned IP changes.

I also configured my router, opening ports 80 (HTTP) and 443 (HTTPS) and forwarding them to my Raspberry Pi, then confirming these rules were properly applied and active.

DNS propagation took some time. Using tools like dig and dnschecker.org, I could confirm my DuckDNS domain resolved correctly from various global locations.

Once this step was complete, my server was reachable from anywhere in the world.

Securing the Setup: HTTPS and Firewall Hardening

Running a public-facing server required taking security seriously.

First, I installed UFW, the Uncomplicated Firewall, allowing only ports 22 (SSH), 80, and 443, and blocking all other inbound traffic.

Next, I set up Fail2Ban to monitor for suspicious activity and automatically block IPs with repeated failed login attempts.

For HTTPS, I used Certbot to generate free Let's Encrypt certificates, configuring Nginx to serve the Ghost blog via HTTPS and enabling HTTP to HTTPS redirection to enforce secure access.

One hurdle along the way: at first, Nginx conflicted with the default site configuration left over from installation. Removing the default site block resolved this cleanly, allowing my domain to serve traffic properly.

With these steps completed, my site was fully encrypted and hardened.

Local Network Access Challenge

Despite external success, I ran into an unexpected issue: while the site was accessible globally, I couldn't reach it from devices inside my home network.

Diagnosis: my router did not support NAT loopback (sometimes called "hairpin NAT"), which prevents devices in the local network from resolving the public domain back to internal resources.

Solution: I edited the hosts file on my PC and MacBook, manually mapping zero-day.is to my Raspberry Pi's internal IP address. This workaround ensured that I could test and develop using my actual domain even from within my own network.

Migrating Offline Blog Content

Before going live, I had already written 15 articles in my offline Ghost instance. Migrating this content was crucial.

I used the Ghost Admin Panel to export all my offline content as a .json file and import it into the live instance.

Hurdle: Ghost doesn't perform duplicate checking during imports. Since I had already published some test posts on the live site, the import process resulted in duplicated articles.

Lesson learned: for clean migrations, it's better to import into an empty database or clean up beforehand.

Post Display Limitation: Solving the "6 Posts" Problem

After migration, I noticed only six posts were visible on my homepage, even though more were published.

First attempt: I edited index.hbs and changed the foreach loop to:

{{#foreach posts limit="all"}}

However, this did not solve the issue.

Further investigation revealed that the number of posts per page is also controlled in the theme's package.json file under the setting posts_per_page.

The fix was to modify package.json:

"posts_per_page": 100

then restart Ghost:

ghost restart

and clear the browser cache to see changes immediately. All posts were displayed as intended.

Final Results

The project reached completion: zero-day.is is globally accessible, fully encrypted, and performs reliably, the blog shows all my posts, and the Raspberry Pi operates efficiently as a dedicated web server, with firewall and security measures in place to protect it.

The satisfaction of seeing my work live on the internet, running on my own hardware, is hard to overstate.

Reflection

This project offered real-world experience across Linux system administration, DNS and dynamic DNS management, web server configuration, security hardening, and practical problem-solving.

What started as an idea to track my learning has turned into a technical achievement. Beyond just writing, I now understand the backbone of how websites operate at a fundamental level.

Next Steps

With the blog live, I plan to automate daily and weekly backups for the database and media files, optimize performance with caching mechanisms and image compression, and keep an eye on SSL certificate renewals to avoid any service interruptions.

This project taught me that building something from scratch, even with hurdles, is the best way to deeply understand the technologies we rely on daily. My learning continues, and zero-day.is will grow with me.

Stay tuned.