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
| Component | Choice | Reason |
|---|---|---|
| VPS | <ofc from EU> | EU-based, GDPR, affordable, solid reputation in the security community |
| OS | Ubuntu 24.04 LTS | Familiar, long-term support, large security community |
| Web server | Nginx | Lightweight, no unnecessary modules |
| TLS | Let's Encrypt + Certbot | Free, automated renewal |
| Domain registrar | Cloudflare | At-cost pricing, no renewal games, free DNS management |
| Domain TLD | .dev | HSTS preloaded — HTTPS enforced by the browser |
| Frontend | Pure HTML/CSS | Zero attack surface from dependencies |
| Password manager | KeePassXC | Open 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
- Open PuTTYgen
- Key type: EdDSA, Curve: Ed25519
- Click Generate and move the mouse to add entropy
- Save the private key as
.ppk(for PuTTY) - 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)
.devdomains are HSTS preloaded — the browser enforces HTTPS, no exceptions
In Cloudflare dashboard → DNS → Records:
| Type | Name | Value | Proxy |
|---|---|---|---|
| A | @ | VPS IP | DNS 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) and22/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 leavesbody: {}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.