svir0x.dev/posts/01-infrastructure
root@svir0x.dev:~$ cat posts/01-infrastructure.html

Building a Security Blog From Scratch — Part 1: Infrastructure

2026-05-08  ·  linux  ·  vps  ·  nginx  ·  tls

No frameworks. No CMS. No bullshit.

Philosophy

This blog exists for one reason: to document real security work without the noise. No SEO tricks. No clickbait headlines. No cookie banners, trackers, or analytics phoning home. Just plain HTML, a hardened server, and content that actually matters.

Full control means full responsibility — and that's the point.

Stack

ComponentChoiceReason
VPS<ofc from EU>EU-based, GDPR, affordable, solid reputation in the security community
OSUbuntu 24.04 LTSFamiliar, long-term support, large security community
Web serverNginxLightweight, no unnecessary modules
TLSLet's Encrypt + CertbotFree, automated renewal
Domain registrarCloudflareAt-cost pricing, no renewal games, free DNS management
Domain TLD.devHSTS preloaded — HTTPS enforced by the browser
FrontendPure HTML/CSSZero attack surface from dependencies
Password managerKeePassXCOpen source, offline, no cloud dependency

Step 1: Provisioning the VPS

Create a new server on VPS provider of your choice

  • Type: We got (2 vCPU, 4 GB RAM, 40 GB SSD) — less than bigmac menu/month
  • Image: Ubuntu 24.04 LTS
  • Hostname: something neutral — not your domain name. We used woman name.
  • SSH key: add your public key during provisioning — do not use password authentication
Never provision a server with password auth enabled. SSH keys only, from day one.

Once the VPS is up, update the system immediately:

sudo apt update && sudo apt upgrade -y
sudo apt autoremove -y

If a new kernel was installed, reboot:

sudo reboot

Step 2: SSH Key Generation

We use PuTTYgen to generate the key pair and KeePassXC to store and manage it via SSH agent.

Generating the key

  1. Open PuTTYgen
  2. Key type: EdDSA, Curve: Ed25519
  3. Click Generate and move the mouse to add entropy
  4. Save the private key as .ppk (for PuTTY)
  5. Conversions → Export OpenSSH key — import this into KeePassXC

KeePassXC SSH Agent

  • Enable SSH Agent in KeePassXC: Settings → SSH Agent → Enable
  • Requires Pageant.exe (part of the PuTTY package) running in the background
  • The key is served automatically to SSH clients when the KeePassXC database is unlocked
On Windows, KeePassXC communicates with PuTTY via Pageant. No need to keep the private key as a loose file on disk.

Step 3: Domain & DNS

  • Domain: svir0x.dev registered via Cloudflare Registrar (at-cost pricing)
  • .dev domains are HSTS preloaded — the browser enforces HTTPS, no exceptions

In Cloudflare dashboard → DNS → Records:

TypeNameValueProxy
A@VPS IPDNS only (grey cloud)
Keep proxy disabled until TLS is configured. Certbot needs direct access to the server on port 80 to complete the HTTP-01 challenge.

After TLS is set up, enabling Cloudflare proxy adds free DDoS protection.

Step 4: Nginx Setup

Install Nginx:

sudo apt install nginx -y
sudo systemctl status nginx

Disable the default site and create a server block for the domain:

sudo unlink /etc/nginx/sites-enabled/default
sudo nano /etc/nginx/sites-available/svir0x.dev

Minimal server block config:

server {
    listen 80;
    listen [::]:80;
    server_name svir0x.dev;

    root /var/www/svir0x.dev;
    index index.html;

    server_tokens off;

    location / {
        try_files $uri $uri/ =404;
    }
}

Enable the site and verify config:

sudo ln -s /etc/nginx/sites-available/svir0x.dev /etc/nginx/sites-enabled/
sudo nginx -t

Expected output: syntax is ok and test is successful.

server_tokens off hides the Nginx version from response headers and error pages — no reason to advertise it.

Step 5: Firewall (ufw)

Allow only the ports we actually need:

sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable

Verify:

sudo ufw status
OpenSSH is an alias for port 22/tcp. Always allow SSH before enabling ufw — or you lock yourself out.
Pro tip — provider firewall: Most VPS providers offer a cloud-level firewall that sits in front of the server, before traffic even reaches ufw. For an Nginx server with SSH access, only two ports are needed: 443/tcp (HTTPS) and 22/tcp (SSH). Port 80 can be dropped at the provider level — Certbot's HTTP-01 challenge only runs once during initial setup, and all ongoing HTTP traffic should be redirected to HTTPS by Nginx anyway. If you have a static IP address (home or office), consider restricting port 22 to that IP only. Exposing SSH to the entire internet is the default — it doesn't have to be.

Step 6: TLS with Certbot

Install Certbot with the Nginx plugin:

sudo apt install certbot python3-certbot-nginx -y

Obtain and deploy the certificate:

sudo certbot --nginx -d svir0x.dev

Certbot completes the HTTP-01 challenge on port 80, obtains the certificate, and automatically updates the Nginx config to serve HTTPS and redirect HTTP to HTTPS.

Verify the certificate and check auto-renewal timer:

sudo certbot certificates
sudo systemctl status certbot.timer

certbot certificates should show the domain with a valid expiry date. The systemd timer runs twice daily and renews certificates automatically when they are within 30 days of expiry.

Certbot 2.9.0 on Ubuntu 24.04 leaves body: {} in the local account JSON (/etc/letsencrypt/accounts/.../regr.json). This is a known cosmetic bug — the account is valid on the Let's Encrypt side and renewal works correctly in practice.

Nginx config after Certbot

Certbot modifies the server block automatically. The result:

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name svir0x.dev;

    ssl_certificate /etc/letsencrypt/live/svir0x.dev/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/svir0x.dev/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    root /var/www/svir0x.dev;
    index index.html;

    server_tokens off;

    location / {
        try_files $uri $uri/ =404;
    }
}

server {
    listen 80;
    server_name svir0x.dev;
    return 301 https://$host$request_uri;
}

Cloudflare — enable proxy and set SSL mode

Now that TLS is working, switch the DNS record to Proxied (orange cloud) in the Cloudflare dashboard. Then set SSL/TLS mode to Full (strict) — this verifies that the certificate on the server is valid, not self-signed.


Next: Part 2 — Server Hardening