Nginx Server Block Generator
Build the server { } blocks that make Nginx serve your domains, local applications and
static websites — without hand-writing the config or copying half-broken snippets. Pick a starting
preset (static site on your LAN, HTTPS with Let's Encrypt, a single-page app, or a local app behind a
proxy), add one card per virtual host, and get a live Nginx site file you can drop straight into
/etc/nginx/sites-available/. The generator writes the parts people forget — the
HTTP→HTTPS redirect, the ACME webroot so certbot can renew, the SPA fallback so deep links don't
404, the forwarded-IP headers your app needs — and checks the mistakes that actually break site
files: TLS with no certificate paths, a proxy pointed at Nginx's own port, duplicate
server_names, a relative root.
1 · Starting point
Pick a preset to fill the form with a working baseline. Choosing a preset replaces the current sites — everything can be edited afterwards.
2 · Sites (one card per virtual host)
Each site gets its own server block. List every domain the block should answer for in Server name(s) — space-separated, e.g. example.com www.example.com. A Default (catch-all) site absorbs traffic that matches nothing else.
3 · Global settings
/404.html. Leave empty for Nginx's default error page.Import an existing config (optional)
Paste an nginx site file (e.g. from cat /etc/nginx/sites-available/mysite) or upload it — it is parsed in your browser and loaded into the form above. One card per server block: static sites keep their root/index, proxied ones keep their backend host and port, and TLS settings, certificate paths, the HTTP→HTTPS redirect and the ACME webroot are picked up too. Everything else is reported and left out.
Your Nginx server blocks
Install it (three commands)
Run on the server. Step 3 is the safety net: nginx -t validates the file and only reloads if it parses — a typo can't take Nginx (and every site behind it) down.
How virtual hosting works
Nginx can serve hundreds of sites from one machine because every HTTP request carries a Host header. Each server { } block declares the names it answers for in its server_name; when a request arrives, Nginx matches the Host header against those lists and hands the request to the matching block. No match? It goes to the block flagged default_server — which is why a catch-all card exists here.
That one mechanism covers everything on this page: example.com and www.example.com can share a block, your static site and your proxied app live in separate blocks on the same ports, and a default block drops (or redirects) traffic for names you don't host. The only real constraint is that two blocks must not claim the same name on the same port — Nginx will serve whichever it reads first, and your edits to the "right" file will appear to do nothing.
The mistakes that actually break server blocks
- SPAs 404 on refresh. A single-page app serves every route from
index.html, but Nginx's default behaviour asks the filesystem for/settings, finds no such file, and answers 404. The fix istry_files $uri $uri/ /index.html;— tick Single-page app on the site card and this generator writes it. - 403 Forbidden from a missing index file. With
autoindex off(the default), a request to the site root needs one of the files listed inindex. Ifroot /var/www/mysite;points at a folder that has noindex.html, every visitor gets 403. The generator flags an emptyindex; check the folder withls /var/www/mysite. - The proxy points at its own port.
proxy_pass http://127.0.0.1:80where Nginx itself listens on 80 is an infinite loop — requests bounce forever until they time out. The generator flags any backend port that equals the listen port. - The redirect swallows certbot. A server-level
return 301 https://...runs before location matching, so an ACME challenge location can never be reached and renewals fail. This generator puts the redirect insidelocation /, leaving/.well-known/acme-challenge/free — a certificate that can't renew is a site that breaks in ~90 days. - Duplicate
server_nameacross site files. Two blocks claiming the same name (often in two different files undersites-enabled/) and Nginx serves whichever it reads first. The generator flags duplicates within this config; check old files withgrep -r server_name /etc/nginx/sites-enabled/. - The stock default site still wins. Debian ships
sites-enabled/default, which is adefault_serveron port 80. If your real site isn't the default, unmatched Hosts keep landing on it — the install commands remove it, and the optional catch-all card lets you decide what happens to unknown names afterwards. - Apps see 127.0.0.1 instead of the real client. Without
X-Forwarded-For/X-Real-IP, every request in your app's logs looks like it came from localhost, and rate limiters or geo features misbehave. This generator always sends the forwarded-IP headers on proxied locations.
Key directives reference
| Directive | What it does |
|---|---|
listen 80; / listen 443 ssl; | The ports this block answers on. ssl terminates TLS here (the certificate lives in Nginx, not in every app). |
server_name | The Host names this block answers for. Space-separated; _ is the conventional "nothing" for a catch-all. |
default_server | Flags this block as the fallback for unmatched Hosts on that port. Only one block per port can claim it. |
root /var/www/mysite; | The folder static files are served from. Must be an absolute path; the request path is appended to it. |
index index.html; | Files tried for a directory request, in order. None found + autoindex off = 403. |
try_files $uri $uri/ /index.html; | The SPA fallback: try the real file, then the directory, then hand everything else to the app shell so client-side routing works on refresh. |
location ~* \.(css|js|...)$ | Regex location for static assets. ~* = case-insensitive; regex locations beat prefix ones, so assets get their own rules (like expires 30d;) regardless of other locations. |
proxy_pass http://127.0.0.1:8000; | Where the request actually goes for a proxied site. No trailing slash = the full path is forwarded unchanged. |
proxy_set_header Host $host; | Forward the original hostname (not 127.0.0.1) so virtual-hosted apps and URL generation work behind the proxy. |
return 301 https://$host$request_uri; | The permanent HTTP→HTTPS redirect. Placed inside location / here so the ACME challenge path is still reachable for certbot. |
error_page 404 /404.html; | Serve your own not-found page instead of Nginx's default. The file must exist inside the site's root. |
client_max_body_size | Largest request body Nginx accepts before answering 413. The default is 1M — uploads bigger than that fail at the proxy, not the app. |
Related tools
The rest of the home-lab wiring that goes with a server block:
Suggested
Luke Keam | techgeek.biz