Skip to content
ohsudo, Home

Nginx Config Generator

Reverse proxy, TLS, redirects, caching and security headers for Nginx.

● Runs 100% in your browser — nothing you type leaves this page.

Site

Main address

Server

What does nginx serve?
HTTPS
Options
/etc/nginx/sites-available/example.com
# Generated by OhSudo — https://ohsudo.com/tools/nginx-config-generator/
# /etc/nginx/sites-available/example.com
# Plain HTTP: send everyone to HTTPS on the main address
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
# The other name: redirect to the main address
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name www.example.com;
# TLS: TLS 1.2 and 1.3 only
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
return 301 https://example.com$request_uri;
}
# The site
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com;
# TLS: TLS 1.2 and 1.3 only
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
# Basic security headers; "always" adds them to error pages too
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
server_tokens off;
# Largest request body (uploads); bigger ones get "413 Request Entity Too Large"
client_max_body_size 10m;
# Compress text responses
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 5;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml application/json application/javascript application/xml image/svg+xml;
# Everything goes to the app; it gets the real client address and protocol in headers
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;
}
}

First, get the certificate

$ sudo apt install certbot
$ sudo certbot certonly --standalone -d example.com -d www.example.com --pre-hook "systemctl stop nginx" --post-hook "systemctl start nginx"

Certbot stops nginx for a few seconds to answer the Let’s Encrypt check on port 80, then starts it again — also on every automatic renewal.

Then install the config

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

Paste the config into the file, save, test and reload. A failed test changes nothing: nginx keeps running with the old config.

The address bar updates as you go, so you can bookmark or share the result.

How to use it

  1. Enter your domain and choose whether www should work too — one name becomes the main address, the other redirects to it.
  2. Pick what nginx serves: an app behind it that speaks HTTP (Node.js, Python, a container) or static files from a folder. PHP-FPM needs FastCGI rules instead: see the guides for WordPress and Laravel.
  3. Choose HTTPS: a free Let’s Encrypt certificate, your own certificate, or plain HTTP.
  4. Copy the config, then follow the commands under it. They are different on Ubuntu / Debian (sites-available + a link in sites-enabled) and on RHEL / Rocky / AlmaLinux (conf.d).

Always run sudo nginx -t before reloading. If the test fails, nginx keeps running with the old config and tells you the file and line of the problem.

What you get

Warning

HSTS tells browsers to use only HTTPS for your site — for two years. Turn it on only when HTTPS works and will stay that way: if the certificate breaks, visitors can’t click through the warning.

Common commands

Terminal window
sudo nginx -t # check every config file
sudo systemctl reload nginx # apply it without dropping connections
sudo nginx -T | less # the full config nginx actually uses
sudo tail -f /var/log/nginx/error.log # what goes wrong, live
sudo certbot renew --dry-run # test Let's Encrypt renewal

FAQ

Why does Certbot stop nginx?

The generator uses Certbot’s standalone mode: for a few seconds, Certbot itself answers Let’s Encrypt’s check on port 80, so nginx has to step aside. It works on a fresh server with no config yet, and renewals do the same automatically. If you’d rather not stop nginx, use certbot --webroot instead and serve /.well-known/acme-challenge/ from that folder on port 80.

My app gets 502 Bad Gateway behind nginx

nginx is fine; it can’t reach the app. The error log tells you why — see Nginx “502 Bad Gateway”: find the cause in the error log. On RHEL-family systems, SELinux blocks nginx from connecting to apps until you allow it with sudo setsebool -P httpd_can_network_connect 1.

nginx won’t start: “Address already in use”

Another program already has port 80 or 443 — often Apache. See find who is using port 80.

What does “nginx 1.25.1 or newer” change?

Since 1.25.1, HTTP/2 is turned on with http2 on; and the older listen 443 ssl http2; form prints a warning. Ubuntu 24.04 and RHEL 9 ship older versions, so the option is off by default. Check yours with nginx -v.

The browser says “too many redirects”

Two parts of your setup disagree about the right address, often Cloudflare and the HTTPS redirect. See ERR_TOO_MANY_REDIRECTS.

My site gives a 504 Gateway Time-out

Nginx gave up waiting for your application. Find out what’s slow before raising the limit: see Nginx 504 Gateway Time-out.

Certbot failed to get a certificate

Let’s Encrypt has to reach this server from the internet. The error’s type says what went wrong: see Certbot failed to authenticate.

Is there a guide for my app?

Complete configs, with the app’s own settings: Node.js and Express, Next.js, FastAPI, Django, React or Vite, WordPress, Laravel, Docker Compose and Ghost.

I get a 403 Forbidden

The error log says why: a missing index file, permissions, or SELinux. See Nginx 403 Forbidden. To check the headers you’re really sending, use the HTTP Headers Analyzer.

Articles

Learn more

upstream sent too big header
Networking

Nginx "upstream sent too big header while reading response header": raise the right buffer

Fix Nginx "upstream sent too big header while reading response header from upstream" and its 502: measure the headers, then raise the right buffer.

Intermediate · 3 min read

certificate has expired
Security

"Certificate has expired": check the dates, renew it, and find out why it didn't renew

Fix "certificate has expired" and NET::ERR_CERT_DATE_INVALID: check the dates with openssl, renew with Certbot, reload the server, and fix why renewal or clock failed.

Beginner · 4 min read

blocked by CORS policy
Networking

"Blocked by CORS policy": what the browser is really refusing, and how to fix it on the server

Fix "has been blocked by CORS policy: No Access-Control-Allow-Origin header": why it happens, the fix on the server or in Nginx, preflight requests, and cookies.

Intermediate · 4 min read

403 Forbidden
Networking

Nginx "403 Forbidden": read the error log, then fix the index, permissions or SELinux

Fix Nginx "403 Forbidden": the error log says if it's a missing index file, wrong permissions on a parent folder, a deny rule or SELinux. namei checks the whole path.

Beginner · 4 min read

Laravel
Networking

Nginx for Laravel with PHP-FPM: production config, permissions and queues

Deploy Laravel on Nginx and PHP-FPM: a complete HTTPS config pointing at public, permissions for storage, the optimize and queue steps, and the errors you may see.

Intermediate · 3 min read

single-page app
Networking

Nginx for a React or Vite single-page app: routes, caching and an API proxy

Serve a React, Vue or Vite single-page app with Nginx: fix 404s on refresh with try_files, cache hashed assets, avoid caching index.html, and proxy /api to your backend.

Beginner · 3 min read

Gunicorn
Networking

Nginx and Gunicorn for Django: production config, static files and HTTPS

Deploy Django with Gunicorn behind Nginx: a complete HTTPS config, a socket with systemd, static and media files, the settings you need behind a proxy, and common errors.

Intermediate · 3 min read

reverse proxy
Networking

Nginx reverse proxy with Docker Compose: in a container or on the host

Put Nginx in front of Docker Compose services: on the host or in a container, connect by service name, fix "host not found in upstream", keep ports private, add HTTPS.

Intermediate · 3 min read

FastAPI
Networking

Nginx reverse proxy for FastAPI and Uvicorn, with HTTPS and WebSockets

Run FastAPI behind Nginx: a complete HTTPS config, Uvicorn in a systemd service, proxy headers for the real client IP, a subpath, WebSockets, and common errors.

Intermediate · 3 min read

Ghost
Networking

Nginx reverse proxy for Ghost: HTTPS, the url setting and redirect loops

Put Nginx in front of a Ghost blog: a complete HTTPS config, the url setting, X-Forwarded-Proto to avoid redirect loops, upload limits, Docker Compose, and common errors.

Beginner · 3 min read

Next.js
Networking

Nginx for Next.js: a production config with HTTPS, caching and standalone builds

Run Next.js behind Nginx: a complete config with HTTPS and long caching for _next/static, the standalone build, a systemd service, and the errors people run into.

Intermediate · 3 min read

reverse proxy
Networking

Nginx reverse proxy for Node.js and Express, with HTTPS and WebSockets

Set up Nginx in front of a Node.js or Express app: a complete config with HTTPS and WebSockets, a systemd service, trust proxy for real client IPs, and common errors.

Beginner · 3 min read

WordPress
Networking

Nginx for WordPress with PHP-FPM: a complete, safe config

Run WordPress on Nginx and PHP-FPM: a complete HTTPS config, pretty permalinks, blocking PHP in uploads, upload limits, caching and the common errors, step by step.

Intermediate · 3 min read

failed to authenticate some domains
Security

Certbot "failed to authenticate some domains": read the Type, then fix DNS, port 80 or the path

Fix Certbot "failed to authenticate some domains": read the error type, then fix DNS records, an IPv6 AAAA record, a closed port 80, the webroot, Cloudflare and CAA.

Intermediate · 5 min read

ERR_TOO_MANY_REDIRECTS
Networking

ERR_TOO_MANY_REDIRECTS with Nginx and Cloudflare: find the loop and break it

Fix ERR_TOO_MANY_REDIRECTS: follow the loop with curl, then fix Cloudflare Flexible SSL, HTTPS redirects behind a proxy, X-Forwarded-Proto and www redirects that fight.

Intermediate · 4 min read

504 Gateway Time-out
Networking

Nginx "504 Gateway Time-out": find out what's slow before raising the timeout

Fix Nginx "504 Gateway Time-out": read the error log, measure the backend, set proxy_read_timeout or fastcgi_read_timeout, and check Cloudflare and load balancers.

Intermediate · 5 min read

Job for nginx.service failed because the control process exited with error code.
Linux

"Job for nginx.service failed because the control process exited with error code": how to find the cause

Fix "Job for X.service failed because the control process exited with error code": read systemctl status and journalctl, test the config, and fix the usual causes.

Beginner · 5 min read

413 Request Entity Too Large
Networking

Nginx "413 Request Entity Too Large": raise the upload limit in the right place

Fix 413 Request Entity Too Large on uploads: set client_max_body_size in nginx, then the limits of PHP, Node.js, the Kubernetes ingress and any proxy in front.

Beginner · 3 min read

SSL certificate problem: unable to get local issuer certificate
Security

"SSL certificate problem: unable to get local issuer certificate": fix the chain, not the check

Fix "unable to get local issuer certificate" in curl, git, pip, npm and Docker: find the broken link with openssl, then fix the server chain or add the missing CA.

Intermediate · 4 min read

bind() to 0.0.0.0:80 failed (98: Address already in use)
Networking

"Address already in use": find who is using port 80 (or any port)

Fix "bind() failed (98: Address already in use)": find the process holding the port with ss or lsof, then stop it, move it, or change your port.

Beginner · 5 min read

502 Bad Gateway
Networking

Nginx "502 Bad Gateway": find the cause in the error log

Fix nginx 502 Bad Gateway: read error.log, then fix the backend that is down, the wrong address or socket, the crashing app, or SELinux on RHEL.

Intermediate · 5 min read