How to force CloudFlare WAF: mTLS

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 the web server and browser do at the same time.

Classic way

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

mTLS way

Your browser or service server (like API) sends a client certificate to your web server in the same that the web server sends 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 the 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 from having exclusive 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

Webserver

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 a connection without certificate and/or with an 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 a valid certificate is rejected.

Bonus: change the 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 replaces the return code of 496 to 403 or if you want to be quicker, 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.