Server config builders
Build and explain nginx, Caddy, systemd and SSH configs — reverse proxies with Mozilla TLS settings, WebSockets and rate limits (with a limit_req simulation), static sites, hardened systemd services and timers with next run times and an exposure estimate, and ssh_config hosts and tunnels with a diagram — or paste your own and have each line explained and checked.
Added
How to use it
Four config files every self-hoster writes, with a builder for each and an explainer for the ones
you already have: tap any line to see what it does, with a link to the official documentation, and
get the mistakes nginx -t, caddy validate, systemd or ssh would complain about — before you
reload anything.
How to use it
Each tab has Build (a form with presets; the config updates as you type) and Explain my config (paste one). Tap a line of the output for its explanation; problems are marked in the gutter and listed under the code (tap one to jump to it). Copy and Download take the file.
nginx. Reverse proxy or static site, one or more backends (an upstream with round robin,
least_conn or ip_hash, and keep-alive connections), optional WebSocket location (the map
$http_upgrade $connection_upgrade idiom), HTTPS with Mozilla’s intermediate or modern profile,
HTTP→HTTPS redirect, HSTS, HTTP/2 (http2 on;, nginx 1.25.1+), gzip, upload size, and a
per-client rate limit. For static sites: try_files with a single-page-app fallback and
year-long caching of assets.
-
proxy_pass and the trailing slash: tap the
proxy_passline and it works an example through. With a URI part (proxy_pass http://app/;— even a lone/) the matched location prefix is replaced:location /api/+http://app/sends/api/users/42as/users/42. Without one (http://app) the URI is passed unchanged.location /api+http://app/gives//users/42. -
Checks (on the generated config and on yours): unbalanced braces, a missing
;(when the next line’s directive is swallowed as an argument), directives in the wrong context (proxy_passinserver,upstreaminserver), unknown directives, duplicates, a URI part inproxy_passinside a regex location,limit_reqwith an undefined zone, a TLS listener without a certificate, the deprecatedlisten … http2, old TLS versions, WebSocket headers withoutproxy_http_version 1.1, andadd_headerlines in a location that silently drop the server’s headers (HSTS). -
limit_req simulation: one client sends N requests at once, a steady stream, or both; the chart shows which are served at once, queued (and for how long) or refused, and the bucket level. It follows
ngx_http_limit_req_module.c: the per-key excess drains at the rate and grows by one per request; aboveburstthe request is refused; otherwise it waitsexcess ÷ rateseconds — or not at all withnodelay.
Caddy. The site from the nginx tab as a Caddyfile — typically a tenth of the lines, because Caddy
does HTTPS, redirects, forwarded headers and WebSocket upgrades by itself. A URI part becomes
handle_path (which strips the prefix). Rate limiting is a plugin (caddy-ratelimit) and is marked
as such. Pasted Caddyfiles are checked for brace placement (a { only at the end of a line),
unknown directives (proxy and gzip are pointed at their Caddy 2 names), duplicate site
addresses and unknown global options; tapping a site address explains what automatic HTTPS will
do for it (public name, localhost/IP with the local CA, wildcard needing DNS, http://).
systemd. A service — or a service and a timer — with user (or DynamicUser), working directory,
environment, restart policy, network ordering (network-online.target with Wants=), and
hardening at basic or strict.
-
Checks: missing
ExecStart, shell syntax inExecStart(it is not run by a shell), keys in the wrong section or misspelt,Restart=alwayswithType=oneshot,After=network-online.targetwithoutWants=, secrets inEnvironment=(readable by every user), timers without a trigger, invalidOnCalendar=expressions. -
Exposure estimate: each hardening setting, met or not, weighted, scored 0–10 with the same verdict words as
systemd-analyze security(PERFECT, SAFE, OK, MEDIUM, EXPOSED, UNSAFE). It is an approximation with fewer settings and weights modelled on systemd’s table — runsystemd-analyze security <unit>for the real figure. -
OnCalendar: type an expression and get the normalized form and the next elapse times, like
systemd-analyze calendar --iterations=8, in your time zone or UTC. Weekdays (Mon..Fri,Sat,Sun), dates with*, lists, rangesa..b, repetitionsa/step,~for days counted from the end of the month, and the shorthands (daily,weekly,quarterly…).
SSH. Your ~/.ssh/config (kept in this browser, never in links), checked and explained, with an
Add a host form that inserts the block above Host . Type a host name and see what ssh will
use, like ssh -G: blocks are read top to bottom and the *first value of each keyword wins
(IdentityFile and forwards accumulate), so Host must come last — the tool warns when it
does not. Negated patterns (!legacy.internal) and Match host|originalhost|user|all are
evaluated. *Tunnels: local (-L), remote (-R) and SOCKS (-D) forwards through optional jump
hosts (-J), as a command line and as the equivalent config block, with notes (ports below 1024,
GatewayPorts for -R, who resolves the destination name) and a diagram of where each port
listens and who connects to whom (Full screen on the SSH tab shows the diagram alone).
Limits
-
The nginx checks cover structure, contexts and the mistakes listed above for nginx’s own modules; they do not open included files, resolve names, test certificates or know third-party modules (they are reported as unknown). Run
nginx -ton the server. -
The Caddyfile check is structural; subdirectives inside a directive’s block are not validated (use
caddy validate). -
systemd: the exposure score is an estimate, not
systemd-analyze security. OnCalendar time zones other than UTC and the local one are not evaluated; a leap-second:60and years before 1970 are not accepted. -
SSH:
Match exec,canonical,final,localuserandtaggedare not evaluated,Includeis not followed, and tokens other than%hare not expanded. -
TLS profiles are copied from Mozilla’s guidelines 5.7 (ssl-config.mozilla.org); check the generator for later revisions. Let’s Encrypt stopped OCSP in 2025, so stapling is off by default.
Sources: the nginx documentation (directive pages linked from each line) and
ngx_http_limit_req_module.c; Mozilla
Server Side TLS and
ssl-config.mozilla.org; the
Caddyfile documentation;
systemd.service(5), systemd.exec(5),
systemd.timer(5), systemd.time(7) and systemd-analyze(1);
ssh_config(5) and ssh(1).
Linking to an example
/tools/server-config/?<parameters>#<tab> where the tab is nginx, caddy, systemd or ssh.
Share link carries the builder settings (packed into ?s=); pasted configs, your ssh config and
Environment= values are never included.
| Parameter | Meaning | Example |
|---|---|---|
|
a preset (list below) |
|
|
the OnCalendar expression on the systemd tab |
|
|
the host name resolved on the SSH tab |
|
example= |
Tab | Shows |
|---|---|---|
|
nginx |
Reverse proxy for an app on :3000 with TLS, HSTS and an HTTP→HTTPS redirect (open) |
|
nginx |
Three backends with least_conn, keep-alive and a WebSocket path (open) |
|
nginx |
A single-page app with long-cached assets and gzip (open) |
|
nginx |
10 r/s with burst 5 nodelay: 12 requests at once — 6 served, 6 refused (open) |
|
nginx |
|
|
caddy |
The same reverse proxy as a short Caddyfile (open) |
|
systemd |
A nightly backup service and timer at 02:30 with a random delay and catch-up (open) |
|
systemd |
A long-running Node service locked down with DynamicUser and strict hardening (open) |
|
systemd |
|
|
ssh |
A Postgres tunnel through a bastion: |
|
ssh |
A SOCKS proxy and a reverse tunnel exposing a local dev server (open) |
For example, ?cal=-01 23:00#systemd
lists the next month-end evenings.
Feedback is a gift. Something to improve, a feature to add, or a bug? Tell me.
Runs entirely in your browser; nothing you type leaves this page.