All systems operational Your IP: 216.73.217.36 info@cloudhosting.lv +371 66 66 29 69 Client area

← All questions

How to set up nginx as a reverse proxy with free SSL

This guide shows how to put nginx in front of one or several web applications on a single Linux server, terminate TLS with a free Let's Encrypt certificate, and leave renewal to automation. It is written for anyone running Node.js, Python, Go or similar apps on a VPS or dedicated server who wants one clean HTTPS entry point. Commands are for Debian and Ubuntu; on other distributions only the package manager calls differ.

Why a reverse proxy at all

A reverse proxy is a single process that owns ports 80 and 443 and forwards requests to backend applications listening on local ports. Three practical reasons to run one:

  • One entry point. Only nginx is exposed to the internet. Your apps bind to 127.0.0.1:3000, 127.0.0.1:8080 and so on, and are never reachable directly.
  • TLS termination. Certificates live in one place. Backends speak plain HTTP on localhost and you never touch their TLS code.
  • Several apps on one server. nginx routes by hostname, so app.example.com and api.example.com can be different processes on the same machine and the same IP address.

You also get access logs, gzip, rate limiting and static file serving along the way, but the three points above are the core reason.

Install nginx

sudo apt update
sudo apt install nginx
sudo systemctl enable --now nginx

Verify that it responds:

curl -I http://127.0.0.1

You should see HTTP/1.1 200 OK and a Server: nginx header. If you run a firewall, open both web ports, for example sudo ufw allow "Nginx Full". Before continuing, make sure the DNS A record of your domain points at the server: certbot will fail later without it.

A clean proxy_pass server block

Assume an application listening on port 3000. Create /etc/nginx/sites-available/app.example.com:

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;
    }
}

The headers matter as much as the proxy_pass line:

  • Host tells the backend which domain was requested. Without it the app sees 127.0.0.1:3000 and builds wrong absolute URLs and redirects.
  • X-Forwarded-For carries the real client IP. Without it every visitor appears as 127.0.0.1 in application logs, and rate limiting or banning by IP becomes useless.
  • X-Forwarded-Proto tells the app whether the original request was HTTP or HTTPS, which prevents redirect loops once TLS is on.

Enable the site and reload:

sudo ln -s /etc/nginx/sites-available/app.example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Run nginx -t every time before a reload; it catches syntax errors while the old configuration keeps serving. For a second application, copy the block, change server_name and the port in proxy_pass, done.

Free SSL with certbot and the nginx plugin

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d app.example.com

The nginx plugin proves domain ownership over port 80, obtains a Let's Encrypt certificate and edits your server block: it adds a listen 443 ssl section with the certificate paths and, if you accept the prompt, an HTTP to HTTPS redirect. Accept it. The resulting redirect looks like this, and you can also write it by hand:

server {
    listen 80;
    listen [::]:80;
    server_name app.example.com;
    return 301 https://$host$request_uri;
}

Multiple domains in one command work too: -d app.example.com -d www.app.example.com.

Check that auto-renewal actually works

Let's Encrypt certificates are valid for 90 days. The certbot package installs a systemd timer that runs twice a day and renews anything with less than 30 days left. Trust it only after checking:

sudo certbot renew --dry-run
systemctl list-timers | grep certbot
sudo certbot certificates

The dry run simulates a full renewal without touching the real certificate. If it passes, renewals will work. The plugin reloads nginx after each renewal, so the new certificate is picked up without downtime. Put a calendar note 60 days out anyway and check certbot certificates once; silent renewal failures are rare but expensive.

Common mistakes

  • Missing forwarded headers. The most frequent one. Symptoms: wrong URLs in redirects, all clients logged as 127.0.0.1, login loops after enabling HTTPS. The four proxy_set_header lines above fix all three.
  • WebSockets do not connect. nginx does not forward the protocol upgrade by default. For a WebSocket path add:
    location /ws/ {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_read_timeout 3600s;
        proxy_buffering off;
    }
    Without proxy_read_timeout nginx drops idle sockets after 60 seconds.
  • Buffering breaks streaming. Server-sent events and long-polling responses arrive in one late chunk because nginx buffers the reply. Set proxy_buffering off; for those locations.
  • Uploads fail with 413. The default client_max_body_size is 1m. Raise it in the server block, for example client_max_body_size 50m;.
  • Reloading without testing. One typo takes down every site nginx serves. Make nginx -t a reflex.

Where to run this

Everything above fits comfortably on a small virtual server; nginx itself needs a few megabytes of RAM. If you need a machine to practice on or to host the real project, our VPS plans run in our own Tier 3+ data centre in Riga on our own network. And if a project outgrows domain-validated Let's Encrypt, for example when an online store needs organization validation or a warranty-backed certificate, see our commercial SSL certificates. The nginx configuration stays exactly the same, only the certificate files change.

Rather have us run it for you?
VPS. NVMe virtual servers, live in under a minute.
Learn more

Ready to start?

Deploy in minutes or talk to an engineer about what fits your project.