Repository navigation
feat(nginx): TLS 1.3-only config, site examples and build hardening - #126
Merged
Merged
Conversation
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
The rejection server does not cover QUIC, allowing unknown QUIC connections to reach the real virtual host.
1 open finding
What changed in this PR
Hardens fresh NGINX installations with TLS 1.3-only defaults, handshake rejection, upload sizing, worker tuning, and compiler protections.
Changes:
- Enforces TLS 1.3 and adds a default TLS rejection server.
- Adjusts headers, upload limits, and worker settings.
- Enables compiler hardening flags.
| File | Description |
|---|---|
nginx/nginx_installer.sh |
Updates generated configuration and build flags. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…ardening - ssl_ecdh_curve: keep prime256v1/secp384r1 after X25519. RFC 8446 makes secp256r1 the mandatory group; P-256-only clients (FIPS mode, embedded stacks) failed the handshake with the X25519-only list. - Mark the reject block's quic listeners default_server explicitly. Without it the QUIC default depended on block order, and a quic server added above it served its certificate to unknown-SNI HTTP/3 clients again. - Drop ssl_conf_command Options KTLS: it is a performance knob, not hardening, and without the tls kernel module it silently does nothing (nginx -t never rejects it, contrary to the comment). - Build flags: -U before -D_FORTIFY_SOURCE=3 so Ubuntu's default level 3 is not lowered to 2; drop the redundant -fPIC; add -fstack-clash-protection and full RELRO via --with-ld-opt.
The generated config offers X25519MLKEM768, which OpenSSL only knows from 3.5 on; on older versions nginx -t failed at the very end of the install. Check the system OpenSSL up front (before any package upgrade) and again after the dependencies are installed, and stop with a clear "too old" message instead. openssl is now an explicit apt/dnf dependency, since the check and the self-signed certificate both need the CLI.
Stensel8
force-pushed
the
nginx-tls13-hardening
branch
from
October 12, 2026 11:21
e6ab7a0 to
42954b0
Compare
- The default vhost only listened to "localhost", so on a fresh install every browser visit by hostname hit the catch-all reject block (ERR_SSL_UNRECOGNIZED_NAME_ALERT). server_name and the self-signed certificate now also cover the short hostname and FQDN. Plain IP access sends no SNI and stays refused by design; the installer now says so and lists the URLs that work. - kTLS: ssl_conf_command Options KTLS, and the installer loads the tls kernel module now and at boot (/etc/modules-load.d/nginx-ktls.conf). If the module cannot be loaded it only warns; OpenSSL then falls back to user-space TLS. remove cleans up the modules-load.d entry.
…by IP Revert the hostname/FQDN in server_name and the certificate: anyone reaching the server from outside could read the machine's name from the certificate. Instead of refusing the handshake, the default server is now the welcome vhost itself with the self-signed "localhost" certificate. The bare IP address and every unknown name get that placeholder, so the page is reachable by IP while scanners learn neither the hostname nor the certificate of a real site added later (that site's server block, by name, still gets its own certificate, over TCP and HTTP/3).
Lessons from a real deployment's config: - nginx.conf now includes conf.d/*.conf and stream.d/*.conf and defines the $connection_upgrade map; without those, no site file or websocket proxy could work. - snippets/proxy.conf holds the shared proxy settings (forwarded headers, websocket upgrade, compression off for proxied responses). - Example sites as conf.d/*.conf.example (inactive until copied): Home Assistant, Jellyfin, the *arr apps, Seerr, qBittorrent and Cockpit, each with the app-side setting it needs (trusted proxies, known proxies, Cockpit origins, ...). stream.d/minecraft.conf.example shows a TCP passthrough. - Port 80 serves ACME HTTP-01 challenges from /var/www/acme for every name (certbot --webroot) and redirects the rest to HTTPS.
…root first Found while rebuilding over an existing install on real hardware: - Without sudo the script died with a bare 'Permission denied' on the log file in /var/log. install and remove now check for root before anything logs. - The port check offered to stop the NGINX this script installed, taking every site down for the whole build. It now leaves that one running (recognised by its unit in /etc/systemd/system) and the install ends with a restart instead of a start, so the new binary takes over in one step.
Where a running nginx came from does not matter: it keeps serving during the build and the restart at the end hands over to the new binary.
Six app-specific site files were more than a fresh install needs. Ship three generic examples instead: reverse-proxy (HTTP or HTTPS upstream, websockets), static-site and tcp-passthrough. The app-side settings that made the per-app files useful (trusted proxies, Cockpit origins, ...) move to one table in the README, and the README now mentions DNS-01 for wildcard certificates.
Drop TLS_AES_128_GCM_SHA256, leaving AES-256-GCM and ChaCha20-Poly1305. testssl scored cipher strength 90 because of the 128-bit suite (A+ at 96); every mainstream client also speaks one of the 256-bit suites, so this is full marks at no practical compatibility cost. QUIC Initial packets use AES-128-GCM regardless, HTTP/3 keeps working.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

TLS
X25519MLKEM768(post-quantum hybrid), X25519, P-256, P-384.tlskernel module loaded at boot; warns and falls back where unavailable).Default server
localhostcertificate, so neither the hostname nor a real site's certificate leaks./var/www/acmeand redirects everything else to HTTPS.Serverheader removed.Sites
conf.d/*.confandstream.d/*.confare included;$connection_upgrademap for websockets.reverse-proxy,static-site,tcp-passthrough, plus a sharedsnippets/proxy.conf. App-side proxy settings (Home Assistant, Jellyfin, Cockpit, …) are listed in the README.Installer
_FORTIFY_SOURCE=3, stack-clash protection, full RELRO.Trade-offs
TLS_AES_128_GCM_SHA256(RFC 8446's mandatory suite) is for full marks in SSL Labs-style ratings; every client in testssl's simulation supports a 256-bit suite.Testing