Enforcing mTLS

This is where the policy becomes real. The two configurations below, one on the calling side, one on the serving side, are what turn "both ends should authenticate" into "no valid certificate, no connection." Everything before this page was setup, this is the enforcement.

Recall the two ends of an internal connection: We'll use a WAF as an example, but this can apply to any communication. The WAF must present its client certificate and verify the backend's server certificate, and the backend must present its server certificate and require the WAF's client certificate. Both halves have to be configured.

The calling side (WAF)

On the WAF, inside the location block that proxies to the backend:

location / {
    proxy_pass https://service.int.example;

    # Verify the backend's server certificate against the CA
    proxy_ssl_server_name on;
    proxy_ssl_name service.int.example;
    proxy_ssl_trusted_certificate /etc/ssl/certs/example-ca.crt;
    proxy_ssl_verify on;
    proxy_ssl_verify_depth 2;

    # Present the WAF's own client certificate (this is the "mutual" half)
    proxy_ssl_certificate     /etc/ssl/certs/waf-to-service.crt;
    proxy_ssl_certificate_key /etc/ssl/private/waf-to-service.key;
}

Line by line, what each directive enforces:

The serving side (backend)

On the backend host, the internal gateway server block:

server {
  listen 443 ssl;
  server_name service.int.example;

  # This host's server certificate
  ssl_certificate     /etc/ssl/certs/service.crt;
  ssl_certificate_key /etc/ssl/private/service.key;

  # Require and verify the caller's client certificate
  ssl_client_certificate /etc/ssl/certs/example-ca.crt;
  ssl_verify_client on;
  ssl_verify_depth 2;

  ssl_protocols TLSv1.2 TLSv1.3;
  ssl_ciphers HIGH:!aNULL:!MD5;

  location / {
    # hand off to the local application
    proxy_pass http://127.0.0.1:80;
    proxy_set_header Host $host;
    # ...forwarding headers
  }
}

The three directives that do the enforcing:

The backend terminates the mutually-authenticated TLS, then hands the request to the local application over plain localhost HTTP (127.0.0.1:80). The application itself doesn't need to know anything about certificates. The gateway enforces mTLS in front of it. That keeps the application simple and puts all the certificate logic in one place per host.

Gotchas

Adapt this for…

Any internal service-to-service connection, not just WAF-to-backend. Anywhere two internal components talk and you want that traffic authenticated rather than merely encrypted, this is the shape, and ssl_verify_client on (or its equivalent on the serving side) is the line that enforces it.


Revision #4
Created 2026-07-20 19:36:33 UTC by Chris Landis
Updated 2026-07-27 14:52:29 UTC by Chris Landis