MTLS design

We’ll see how it’s possible to do mutual TLS (mTLS) with nginx and force your users to pass through CloudFlare WAF and reject all direct connections.

What’s mTLS?

mTLS or mutual TLS is a double check that web server and browser do in same time.

Classic way

Your browser check server certificate when accessing your web server. It’s normal way for you.

mTLS way

Your browser or service server (like API) send a client certificate to your web server in same that the web server send his certificate.

The server verifies and if it’s ok, no problem otherwise it rejects with an HTTP 496 SSL Certificate Required (expansion of the 400 Bad Request response code, used when a client certificate is required but not provided).

Why do you need to use it with CloudFlare?

If a person finds IP address of your server, it’s possible to bypass CloudFlare WAF so we need to harden.

mTLS vs IP filtering

You have 2 ways to restrict your web server about having exclusively traffic through CloudFlare.

For a dedicated web server, you can use IP filtering with nftables/iptables but in addition, you need to keep your configuration up to date with CloudFlare ranges (IPv4/IPv6).

When you’re mutualizing your web server like having some virtual hosts with CloudFlare WAF and others without CloudFlare WAF, your only way is mTLS and in addition, it’s more easy and secure because forged packets with spoofed IP origin still fail due to mTLS.

Enabling mTLS

Web server

You need to do very little configuration:

  • download CloudFlare’s CA certificate used for mTLS

    cd /etc/nginx
    wget -N -O cloudflare-mtls.pem https://support.cloudflare.com/hc/en-us/article_attachments/360044928032/origin-pull-ca.pem
    
  • add 2 lines in your virtual host

    ssl_client_certificate /etc/nginx/cloudflare-mtls.pem;
    ssl_verify_client on;
    

Never change ssl_verify_client to another possibility otherwise you’ll accept connection without certificate and/or with invalid certificate.

  • restart nginx

    systemctl restart nginx
    

On CloudFlare

On your domain dashboard, in SSL/TLS, you need to activate Authenticated Origin Pulls

Conclusion and bonus

All done, you’re using mTLS for your virtual host with strict mode so any connection without valid certificate is rejected.

Bonus: change return code when no client certificate

In your virtual host config, inside your location, you can add:

if ($ssl_client_verify != SUCCESS) { return 403; }

This replace the return code of 496 to 403 or if you want to be more quick, you can prefer 444 Connection Closed Without Response.

444 is a non-standard status code used to instruct nginx to close the connection without sending a response to the client, most commonly used to deny malicious or malformed requests.

This status code is not seen by the client, it only appears in nginx log files.