Prerequisites
- A domain or subdomain with an
Arecord pointing at your server's IPv4 address - Ports 80 and 443 open in the firewall
- An application listening locally, for example on
127.0.0.1:3000
Check DNS first, because certificate issuance fails until it resolves: dig +short app.example.com should print your server's address.
1. Install nginx and Certbot
Ubuntu and Debian:
sudo apt install -y nginx certbot python3-certbot-nginx
AlmaLinux and Rocky Linux:
sudo dnf install -y epel-release
sudo dnf install -y nginx certbot python3-certbot-nginx
sudo systemctl enable --now nginx
sudo setsebool -P httpd_can_network_connect 1
The last command allows nginx to connect to a local application port under SELinux.
2. Create the proxy configuration
Create /etc/nginx/conf.d/app.example.com.conf:
server {
listen 80;
listen [::]:80;
server_name app.example.com;
client_max_body_size 25m;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 60s;
}
}
The Upgrade and Connection headers let WebSockets pass through. Test and reload:
sudo nginx -t && sudo systemctl reload nginx
3. Issue the certificate
sudo certbot --nginx -d app.example.com --redirect -m [email protected] --agree-tos --no-eff-email
Certbot proves you control the domain, installs the certificate into your server block, and adds a permanent redirect from HTTP to HTTPS.
4. Confirm renewal
Certificates last 90 days. The package installs a systemd timer that renews them automatically; check it and do a dry run:
systemctl list-timers | grep certbot
sudo certbot renew --dry-run
5. Sensible hardening
Add these inside the HTTPS server block that Certbot created:
add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-Content-Type-Options nosniff always;
add_header Referrer-Policy strict-origin-when-cross-origin always;
Only add HSTS once you are sure the site will stay on HTTPS, because browsers remember it for the full max-age.
Several apps on one server
Repeat step 2 with a different server_name and upstream port for each application, then run Certbot for each name. One Small or Medium server can front a dozen low-traffic services this way.
Troubleshooting
- 502 Bad Gateway: the app is not listening where
proxy_passpoints. Check withss -tlnp. - Certificate challenge failed: DNS does not point at this server yet, or port 80 is blocked.
- Redirect loop: the application is also redirecting to HTTPS; tell it to trust
X-Forwarded-Proto. - Large uploads fail: raise
client_max_body_size.
