Nginx as a reverse proxy with HTTPS
Put Nginx in front of an app on localhost: a server block that proxies to the port, forwards the client headers, supports WebSockets, and gets a free Let's Encrypt certificate from Certbot with auto-renewal.
Drafted with AI from official documentation. Scripts were run in a test environment and repository links verified. Awaiting a human check. Did it work for you? Last edited Outdated?
You have an application listening on a port, say 127.0.0.1:3000, and a domain pointing at the server. This guide gives it a proper https://app.example.com address with Nginx. It assumes Ubuntu or Debian; on RHEL-family systems the package name is the same and site files go in /etc/nginx/conf.d/ instead of sites-available.
1. Install Nginx and open the firewall
Check before you run this
- Runs with administrator rights. Make sure you understand every line.
sudo apt update
sudo apt install nginx
sudo systemctl enable --now nginx
sudo ufw allow 'Nginx Full' # ports 80 and 443, if you use ufw
curl -I http://localhost
The last command should return HTTP/1.1 200 OK from the default welcome page. Make sure the DNS A record (and AAAA if you have IPv6) for app.example.com points at this server before you ask for a certificate; Let’s Encrypt has to be able to reach it.
2. Write the server block
Create /etc/nginx/sites-available/app.example.com. Start with plain HTTP; Certbot will add HTTPS in the next step.
server {
listen 80;
listen [::]:80;
server_name app.example.com;
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_passis the address of your application. No trailing slash here means the request path is passed through unchanged.- The
proxy_set_headerlines tell the app who the real client is and that the request arrived over HTTPS. WithoutHost, the app sees127.0.0.1:3000. UpgradeandConnectiontogether withproxy_http_version 1.1make WebSockets work (Socket.IO, live reload, chat apps). They are harmless for ordinary requests.- Nginx waits 60 seconds for the app by default. For long-running requests add
proxy_read_timeout 300s;in the location.
3. Enable the site and reload
Check before you run this
- Runs with administrator rights. Make sure you understand every line.
sudo ln -s /etc/nginx/sites-available/app.example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
curl -I -H 'Host: app.example.com' http://127.0.0.1
nginx -t checks the configuration without touching the running server; never reload without it. Careful: reload replaces the live configuration for every site on this server. If something breaks, remove the symlink in sites-enabled, run nginx -t and reload again to return to the previous state.
4. Get a certificate with Certbot
Check before you run this
- Runs with administrator rights. Make sure you understand every line.
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d app.example.com
Certbot asks for an email address for expiry warnings, proves to Let’s Encrypt that you control the domain by answering a challenge through your Nginx, saves the certificate under /etc/letsencrypt/live/app.example.com/, and edits the server block: it adds a listen 443 ssl server and a redirect from HTTP to HTTPS. Open https://app.example.com in a browser. Careful: Certbot modifies your Nginx config in place; it keeps the original under /etc/letsencrypt/ but a copy of your own (sudo cp before running it) is the simplest way back.
Renewal is automatic. The package installs a systemd timer (or cron job) that runs certbot renew twice a day and reloads Nginx when a certificate was replaced. Test it without waiting:
Check before you run this
- Runs with administrator rights. Make sure you understand every line.
sudo certbot renew --dry-run
systemctl list-timers | grep certbot
What the finished config looks like
After Certbot, with a few additions that are worth making by hand:
server {
listen 80;
listen [::]:80;
server_name app.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name app.example.com;
ssl_certificate /etc/letsencrypt/live/app.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
client_max_body_size 20m;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff always;
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";
}
}
http2 on;is the current syntax (Nginx 1.25.1 and newer). Older versions uselisten 443 ssl http2;and will warn about the new form.client_max_body_sizedefaults to 1 MB, which is the cause of413 Request Entity Too Largeon file uploads.options-ssl-nginx.confis Certbot’s maintained list of safe TLS protocols and ciphers; prefer it to copying settings from old blog posts.- The security headers are a start; see the headers reference for the full list.
Several apps on one server
Add one server block per host name, each with its own server_name and proxy_pass, and run certbot --nginx -d other.example.com for each. To split by path instead, add locations in the same block:
location /api/ {
proxy_pass http://127.0.0.1:8000/;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
Here the trailing slash on proxy_pass matters: a request for /api/users reaches the backend as /users. Without the slash it would arrive as /api/users.
Checking and debugging
Check before you run this
- Runs with administrator rights. Make sure you understand every line.
sudo nginx -t # syntax
sudo tail -f /var/log/nginx/error.log # why requests fail
sudo tail -f /var/log/nginx/access.log # what arrives
curl -vI https://app.example.com # certificate and headers as a client sees them
sudo ss -ltnp | grep -E ':(80|443|3000)' # who is listening
502 Bad Gateway means Nginx is fine but could not reach your app: it is not running, listens on another port, or SELinux is blocking the connection. There is a separate entry for that error.
Official documentation
Did this work for you?
No votes yet. Be the first.
If it did not work, the report form below tells the editors what went wrong.
See also
- 0279 What is a reverse proxy? A reverse proxy is a server that sits in front of your apps, receives all requests and forwards them to the right… Beginner · Explains an idea · Any system Article Web servers & reverse proxies 15 Oct 2026
- 0408 What a reverse proxy is and when you need one A reverse proxy is a server that sits in front of your application, accepts requests from the internet and forwards them to… Beginner · Explains an idea · Any system Article Web servers & reverse proxies 15 Oct 2026
- 0410 Caddy: HTTPS reverse proxy in five lines Caddy gets and renews TLS certificates on its own. Install it, write a Caddyfile with your domain and the port your app… Beginner · Step by step · Linux Article Deploying sites · Web servers & reverse proxies 15 Oct 2026
- 0411 CORS explained, and how to fix the browser error The browser blocks a request from one origin to another unless the server allows it with Access-Control headers. Fix it in the… Intermediate · Step by step · Any system, Browser Article Making requests · Web servers & reverse proxies 15 Oct 2026
- 0412 Deploy a static site: from folder to a URL Take a folder of HTML, CSS and JavaScript and publish it at your own domain with HTTPS on a small Linux server… Beginner · Guided project · Linux, macOS Article Deploying sites · Web servers & reverse proxies 15 Oct 2026
- 0413 Fix: 502 Bad Gateway from Nginx Nginx is running but could not get a valid response from your application. Read the error log, check that the app is… Intermediate · Fixes an error · Linux Article Web servers & reverse proxies 15 Oct 2026
Repos in the wild
- Caddy: the web server with automatic HTTPS caddyserver/caddy Caddy is an open-source web server and reverse proxy written in Go that obtains and renews TLS certificates on its own, with a configuration file short enough to memorize and HTTP/1, 2 and 3 support out of the box. GitHub
- Traefik: a reverse proxy that configures itself from Docker and Kubernetes traefik/traefik Traefik is an open-source edge router that discovers services from Docker labels, Kubernetes resources and other providers and routes traffic to them, with automatic Let's Encrypt certificates and a dashboard. GitHub
History
Every saved edit is kept. The plot on the sleeve draws one line per edit. Open any edit to see exactly what changed.
- Dominik +6,417 chars