Chris Collins

Tools · infra

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_pass line 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/42 as /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_pass in server, upstream in server), unknown directives, duplicates, a URI part in proxy_pass inside a regex location, limit_req with an undefined zone, a TLS listener without a certificate, the deprecated listen … http2, old TLS versions, WebSocket headers without proxy_http_version 1.1, and add_header lines 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; above burst the request is refused; otherwise it waits excess ÷ rate seconds — or not at all with nodelay.

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 in ExecStart (it is not run by a shell), keys in the wrong section or misspelt, Restart=always with Type=oneshot, After=network-online.target without Wants=, secrets in Environment= (readable by every user), timers without a trigger, invalid OnCalendar= 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 — run systemd-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, ranges a..b, repetitions a/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 -t on 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 :60 and years before 1970 are not accepted.

  • SSH: Match exec, canonical, final, localuser and tagged are not evaluated, Include is not followed, and tokens other than %h are 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

example

a preset (list below)

ratelimit

cal

the OnCalendar expression on the systemd tab

Mon..Fri --* 09:00:00

host

the host name resolved on the SSH tab

app1.internal

example= Tab Shows

proxy-tls

nginx

Reverse proxy for an app on :3000 with TLS, HSTS and an HTTP→HTTPS redirect (open)

lb-ws

nginx

Three backends with least_conn, keep-alive and a WebSocket path (open)

spa

nginx

A single-page app with long-cached assets and gzip (open)

ratelimit

nginx

10 r/s with burst 5 nodelay: 12 requests at once — 6 served, 6 refused (open)

strip-prefix

nginx

location /api/ + proxy_pass …/: /api/users reaches the backend as /users (open)

caddy-proxy

caddy

The same reverse proxy as a short Caddyfile (open)

timer-backup

systemd

A nightly backup service and timer at 02:30 with a random delay and catch-up (open)

hardened

systemd

A long-running Node service locked down with DynamicUser and strict hardening (open)

calendar

systemd

Mon..Fri --* 09:00:00 and its next elapses (open)

ssh-jump

ssh

A Postgres tunnel through a bastion: ssh -N -L 5432:db.internal:5432 -J … (open)

ssh-socks

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.

Runs entirely in your browser; nothing you type leaves this page.