nginx in reverse-proxy mode is the standard front door for self-hosted services: one machine, one pair of ports (80/443), many backends — each with its own hostname or path, all sharing TLS and logging. A first proxy is a twenty-line config away.
The idea #
client -> nginx :443 -> app on 127.0.0.1:8080
-> app on 127.0.0.1:3000
Clients speak to nginx; nginx forwards to services that never face the internet directly. That indirection buys TLS in one place, virtual hosting for several domains, and zero exposure for the apps.
A first proxy #
server {
listen 80;
server_name app.aospix.com;
location / {
proxy_pass http://127.0.0.1:8080;
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;
}
}
Enable, test, reload — the loop that never changes:
$ sudo ln -s /etc/nginx/sites-available/aospix /etc/nginx/sites-enabled/
$ sudo nginx -t # config test — always
$ sudo systemctl reload nginx # zero-downtime reload
The three X-Forwarded-* headers are not decoration: they are how the backend learns the real client IP and scheme, and apps behind proxies mis-render URLs without them.
The patterns that come next #
| Pattern | Configuration |
|---|---|
| Serve static, proxy the API | location /api/ { proxy_pass ...; } and a root for files |
| WebSockets | proxy_http_version 1.1; + Upgrade/Connection headers |
| Several apps by name | One server {} block per server_name |
| Larger uploads | client_max_body_size 20m; |
| Timeouts for slow apps | proxy_read_timeout 90s; |
TLS for the same block is one certbot command away — it edits the config, obtains certificates, and arranges renewal — and the concepts behind the pattern are worth one read.
When something does not work #
The debugging ladder, in order:
$ sudo nginx -T | grep -A5 server_name # what config is actually loaded
$ curl -H 'Host: app.aospix.com' http://localhost/ # nginx layer
$ curl http://127.0.0.1:8080/health # backend layer
$ sudo tail -f /var/log/nginx/error.log # the answers
Step 2 is the trick worth remembering: curl with a Host header tests the virtual host without touching DNS — splitting "nginx is wrong" from "DNS points elsewhere" (dig settles the latter).
Reload, never restart, for config changes — reload hands over sockets gracefully. And keep nginx -t between every edit: it parses the whole config and catches the missing-semicolon outage before your users do.
For the certificate side, see certbot; for the automatic-TLS alternative, Caddy. The network series continues.
