Skip to content
Commitpedia
Log in Join & edit

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?

Edit history of Nginx as a reverse proxy with HTTPS: 1 recorded edit
Edit history · 1 edit
Type
Article
Edits
1
Editors
1
First issued
15 Oct 2026
Last edit
15 Oct 2026
Reading
4 min

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_pass is the address of your application. No trailing slash here means the request path is passed through unchanged.
  • The proxy_set_header lines tell the app who the real client is and that the request arrived over HTTPS. Without Host, the app sees 127.0.0.1:3000.
  • Upgrade and Connection together with proxy_http_version 1.1 make 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 use listen 443 ssl http2; and will warn about the new form.
  • client_max_body_size defaults to 1 MB, which is the cause of 413 Request Entity Too Large on file uploads.
  • options-ssl-nginx.conf is 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

History

Every saved edit is kept. The plot on the sleeve draws one line per edit. Open any edit to see exactly what changed.

  1. Dominik +6,417 chars

Report a problem

Something wrong, unsafe or copied? Tell the editors.
What is wrong?