SERIES

Hardening Nginx, edge inward

Five guides that are usually read as separate recipes. Taken in this order they are one project: moving a public Nginx server from it serves TLS to it decides who may open a connection, what their browser may execute, how the bytes travel, how little of the disk is reachable, and what happens to whatever is still hostile.

5 articles

Why these five belong together

Every one of these articles answers a question that only becomes well-posed once the previous one is settled. Rate limiting a request is a different problem depending on whether you know who sent it. Choosing a Content Security Policy is a different problem depending on whether the pages you serve come from disk or are synthesized. A configuration is not hardened because it accumulated directives; it is hardened because a sequence of decisions was made in an order where each one narrows the next.

They also share a spine that is easy to miss when they are read individually: push every decision as early in the request path as it will go. Mutual TLS rejects during the handshake, before a single HTTP byte is parsed. CSP moves an execution decision out of your server and into the browser, where the attack actually lands. QUIC moves loss recovery below HTTP so a single lost packet stops stalling unrelated streams. A virtual file answers without a filesystem lookup. A tarpit spends the attacker's connection budget instead of yours. Same instinct, five layers.

The last thing they have in common is provenance. All five run on the machine serving this page. The configuration blocks are quoted from a production server rather than assembled for the article, the version numbers are the ones actually deployed, and the traffic discussed in the tarpit piece is traffic that arrived at this host. That is also why the series is worth reading in order rather than skimmed: the trade-offs described are ones that had to be lived with, not ones proposed and abandoned.

Read in this order

Each entry below states what it decides and what it assumes. They stand alone if you only need one, but the order is the one in which the decisions genuinely constrain each other.

  1. Part 1

    Mastering Mutual TLS (mTLS) with Nginx: A Deep Dive

    Start here, because this is the only decision that changes who can open a connection at all. Everything after it — headers, routing, rate limits — is a conversation with a party you already admitted. The article builds a certificate authority, issues client certificates, and then spends most of its length on the part shorter guides skip: revocation. CRL and OCSP are where mutual TLS becomes an operational commitment rather than a configuration flag, because issuing credentials is easy and withdrawing them is the part you will actually need under pressure.

  2. Part 2

    Content Security Policy (CSP) with Nginx: The Complete Guide

    With the connection legitimate, the next decision is what the browser may execute with what you send it. This is where most hardening attempts stall: the naive policy either breaks the site or contains a wildcard that makes it decorative. It is the longest article of the five, and the one with the most documented failure modes — nonce versus hash strategies, how strict-dynamic changes what your allowlist even means, and how a policy that scores well on a report card can still be bypassed. Read it before you touch the transport layer; a fast site with an unenforceable policy is the wrong trade.

  3. Part 3

    Mastering QUIC and HTTP/3 with Nginx: The Complete Guide

    Transport comes third because it changes performance and failure behavior, not who is allowed in. QUIC replaces the TCP+TLS handshake with one that is fewer round trips and encrypted almost end to end, and HTTP/3 removes the head-of-line blocking that made a single lost packet stall every stream on a connection. The article covers the parts that bite in production: the build requirements, the Alt-Svc advertisement dance, and the 0-RTT replay caveat, which is a correctness problem and not a tuning knob.

  4. Part 4

    Mastering Virtual Files in Nginx: A Complete Guide

    The shortest article, and the cheapest reduction in attack surface of the five. A response the server synthesizes has no path behind it, so there is nothing to traverse, symlink, or race. It is also where root versus alias versus try_files finally becomes concrete — three directives responsible for a large share of accidental file exposure in real Nginx configurations, because their difference is one trailing slash and a silent semantic change. Read it after the first three: it is the piece that makes the surface you just hardened smaller.

  5. Part 5

    Implementing a Tarpit in Nginx: Trap Malicious Scanners

    The final question is what to do with the traffic that has been refused everything above and keeps arriving. A tarpit answers deliberately slowly, holding the scanner's connection open and consuming its concurrency budget rather than your CPU, then feeds the offending addresses into a blocklist that the earlier layers enforce. It is last on purpose: it assumes the decisions before it are already in place, and it is the only one of the five whose effectiveness you can watch happen in a log in real time.

Where this goes next

The natural continuation is downward, into the network the web server sits on: the router that terminates the ISP link, hands out the addresses these virtual hosts bind to, and drops most hostile traffic before Nginx ever sees a SYN. That is a separate series, linked below.

Upward, the continuation is operational rather than architectural. None of these five decisions survives without a way to notice when it breaks: a CSP that silently blocks a legitimate script, a client certificate quietly expiring, an HTTP/3 listener that a firmware update stopped forwarding. Each article ends with the specific check that catches its own failure mode, and those checks are worth wiring into monitoring rather than running by hand once.

What this series does not cover

There is no web application firewall here and no rule-set tuning. That is a deliberate omission: a WAF is a pattern matcher bolted on top of decisions that these five articles make structurally, and reaching for one first tends to paper over an edge that was never constrained properly. There is also no ingress-controller chapter — the configurations are written for an Nginx you administer directly, and translating them into an annotated Kubernetes resource is a different exercise with different failure modes.

← Back to all posts