Configure pfSense HAProxy mTLS (Client Certificate Authentication) for specific subdomains

Kovasky Buezo | Mar 15, 2026 min read

Intro

I’m hosting ERPNext and Nextcloud for my family. Since these services contain more sensitive data, I wanted an extra layer of protection that limits access to approved devices without having to manage a VPN or keep track of a dynamic IP whitelist.

The solution I landed on was mTLS (client certificate authentication), where the reverse proxy requests the client to present a valid certificate signed by an approved CA. This post shows exactly how I set it up on pfSense 2.7.2 using HAProxy-devel 0.63_2.

Create an Internal (self-signed) Certificate Authority

Navigate to System -> Certificates -> Authorities -> Add.

Create an internal Certificate Authority. This CA will be used to sign the client certs that are allowed to access mTLS-protected frontends.

pfSense internal (self-signed) certificate authority.

Generate a client certificate

Navigate to System -> Certificates -> Certificates -> Add/Sign.

Create a new internal certificate and select the CA you created in the previous step. This certificate will be installed on the client devices that you want to allow through.

mTLS internal certificate signed by a self-signed Certificate Authority.

I recommend creating one client certificate per device/user for more granular control. If multiple devices share the same certificate, revoking it means having to issue a new one and reinstall it everywhere.

Export it as PKCS#12

Export the certificate as PKCS#12 (this includes the private key). This is the file you’ll import into your client devices. Keep this safe as anyone with this file can authenticate.

Create a backend for your app

Navigate to Services -> HAProxy -> Backend -> Add.

  • Name: nextcloud

Under Server list, add your internal server:

  • Name: nextcloud
  • Forward to: Address + Port
  • Address: the internal server IP
  • Port: the internal server port
  • Encrypt: no if you terminate TLS at HAProxy
HAProxy backend for nextcloud.

Create a shared frontend

Navigate to Services->HAProxy->Frontend->Add.

Create a frontend that will act as the entry point. This will be the primary frontend that other frontends attach to.

  • Name: shared_frontend
  • Listen address: WAN address (IPv4)
  • Port: 443
  • SSL Offloading: enabled
  • Type: http / https (offloading)
HAProxy frontend external address WAN.

Advanced settings

  • Check Use “forwardfor” option
  • Set httpclose to http-keep-alive
HAProxy shared frontend forward for option.

SSL Offloading

This shared frontend should present your wildcard certificate.

  • SNI filter: *
  • Certificate: select your wildcard cert
  • Enable:
    • Add ACL for certificate CommonName
    • Add ACL for certificate Subject Alternative Names
HAProxy shared frontend SNI filter and wildcard certificate selection.

Create a non-mTLS frontend

Navigate to Services->HAProxy->Frontend->Add.

Create a frontend attached to the shared frontend. This frontend will just do regular routing to backends. In most cases you only need to add ACLs and Actions here, since SSL settings are inherited from the shared_frontend.

  • Name: exposed
  • Check Shared Frontend
  • Primary Frontend: select shared_frontend - http
HAProxy shared frontend for regular backends.

Access Control Lists

Add an ACL rule:

  • Name: photos
  • Expression: Host matches:
  • Value: photos.mydomain.com
HAProxy shared frontend for regular backends access control list.

Actions

Add an action:

  • Action: Use Backend
  • Condition acl names: photos
  • Backend: photos_backend
HAProxy shared frontend for regular backends actions.

Create an mTLS-protected frontend

Navigate to Services->HAProxy->Frontend->Add.

Create a frontend attached to the shared frontend. This frontend enforces client certificates before routing to the backend.

  • Name: exposed_mtls
  • Check Shared Frontend
  • Primary Frontend: select shared_frontend - http
HAProxy shared frontend for mTLS protected backends.

Access Control Lists

Add an ACL rule:

  • Name: next
  • Expression: Host matches:
  • Value: next.mydomain.com
HAProxy shared frontend for mTLS protected backends access control list.

Actions

Add an action:

  • Action: Use Backend
  • Condition acl names: next
  • Backend: nextcloud
HAProxy shared frontend for mTLS protected backends actions.

SSL Offloading

Under SSL Offloading:

It is important to explicitly list your domain names in the SNI filter. In my testing, a catch-all * did not work on the mTLS frontend as it would not prompt for a client certificate.

  • SNI filter: next.mydomain.com app2.mydomain.com app3.mydomain.com...
  • Certificate: your wildcard cert (same as the shared frontend)
  • Enable:
    • Add ACL for certificate CommonName
    • Add ACL for certificate Subject Alternative Names
HAProxy shared frontend SSL offloading options.

Client certificate enforcement

Under SSL Offloading - client certificate:

  • Client verification CA certificate: select the internal CA created earlier

This is the setting that forces the browser/device to present a valid client certificate.

HAProxy shared frontend SSL client certificate options.

Final result

Your frontends list should look like this:

HAProxy mTLS protected frontend and regular frontend using a shared frontend.

Restart HAProxy

Restart the HAProxy service to apply everything.

Install the client certificate and access the protected frontend

Copy the exported certificate to your client devices and install it. Then, navigate to the protected frontend and it should prompt a certificate selection like the cover image in this blog post. If no certificates are available, it means that the certificate was not installed properly.

Done!

You now have the ability to protect subdomains with mTLS.