How this Nginx config works

Nginx sits in front of your app and answers on ports 80 and 443. For an app that listens on a port — a Node.js or Next.js server under PM2, or a Docker container published on the host — Nginx is a reverse proxy: it receives the request and passes it to http://127.0.0.1:<port>. For a static site — a Vite, React or Astro build, or plain HTML — Nginx serves the files itself from a document root, and nothing else has to run.

The generator writes one file for one domain. On Debian and Ubuntu it belongs in /etc/nginx/sites-available/, linked into /etc/nginx/sites-enabled/; on the RHEL family put it in /etc/nginx/conf.d/<domain>.conf instead.

What each option adds

  • HTTPS with Let's Encrypt adds a port-80 server that redirects to HTTPS and a port-443 server pointing at the certificate files Certbot writes under /etc/letsencrypt/live/<domain>/.
  • WebSockets adds a map that turns the Upgrade header into the right Connection header, and a long read timeout so idle sockets are not cut after a minute. Socket.IO, Next.js development servers and most real-time apps need it.
  • Next.js asset caching caches /_next/static/ for a year. Those files have a content hash in their names, so a new build produces new names and nothing stale is ever served.
  • Single-page app fallback answers unknown paths with index.html, so client-side routes such as /settings load instead of returning 404.
  • Security headers adds X-Content-Type-Options, X-Frame-Options, Referrer-Policy and, with HTTPS, Strict-Transport-Security.
  • Max upload size sets client_max_body_size. The default is 1 MB, which is why file uploads fail with 413 Request Entity Too Large until it is raised.

Getting the certificate the first time

Certbot has to prove you control the domain before the certificate exists, and Nginx refuses to start with a config that points at certificate files that are not there yet. So the first time:

  1. Point the domain's A record (and AAAA, if the server has IPv6) at the server.
  2. Generate the config with HTTPS off, enable it and reload Nginx.
  3. Run sudo certbot --nginx -d example.com -d www.example.com. Certbot adds the 443 server and the redirect for you.
  4. From then on, regenerate with HTTPS on whenever you change the config.

Certbot installs a timer that renews certificates before they expire; sudo certbot renew --dry-run checks that renewal will work.

Test before you reload

sudo nginx -t parses every config file and reports the first error with its file and line. Run it before every reload: systemctl reload nginx with a broken config keeps the old one running, but a restart or a reboot would leave every site on the server down.

Common problems

502 Bad Gateway

Nginx reached nothing on the upstream port. The app is not running, crashed on start, or listens on a different port. Check pm2 status or docker ps, then curl -I http://127.0.0.1:3000 on the server.

The site shows the default "Welcome to nginx" page

The request matched the default server. Check that server_name matches the domain exactly and that the file is linked into sites-enabled, then remove /etc/nginx/sites-enabled/default if you do not use it.

Redirect loop after enabling HTTPS

Usually a proxy in front of Nginx — Cloudflare with "Flexible" SSL — talks to the server over HTTP while Nginx redirects HTTP to HTTPS. Set Cloudflare to "Full (strict)".

Let Shipown write it

Shipown writes this virtual host for every app and site it deploys, tests it with nginx -t before reloading, asks Certbot for the certificate when you want HTTPS, and puts the previous configuration back if Nginx rejects the new one. See deploying with Shipown.

Frequently asked questions

Where do I put the generated Nginx config?

On Debian and Ubuntu, save it as /etc/nginx/sites-available/<domain> and link it into /etc/nginx/sites-enabled/. On RHEL, Rocky, Alma and Fedora, save it as /etc/nginx/conf.d/<domain>.conf. Then run sudo nginx -t and sudo systemctl reload nginx.

Why does Nginx fail to start after I enable HTTPS?

The config points at certificate files under /etc/letsencrypt/live/<domain>/ that do not exist yet. Enable the HTTP-only version first, run certbot --nginx to issue the certificate, and only then use the HTTPS version.

Should my app listen on 0.0.0.0 or 127.0.0.1?

Behind Nginx, 127.0.0.1. The app is then reachable only through Nginx, so nobody can bypass HTTPS or your headers by going to the port directly.

How do I proxy WebSockets through Nginx?

Pass the Upgrade header and set Connection to upgrade for upgrade requests, use proxy_http_version 1.1, and raise proxy_read_timeout so idle connections are not closed. The WebSockets option in this generator adds all of that.

Does this tool send my configuration anywhere?

No. The config is built in your browser as you type, and nothing is sent to a server.