pfSense: Let's Encrypt Certificates + HAProxy

Networking

5 min readAdvancedLast updated Oct 29, 2026

Two pfSense packages let you publish several internal web services (Nextcloud, a dashboard, a PBX web UI and so on) through a single public IP:

  • ACME gets free Let's Encrypt certificates and renews them automatically.
  • HAProxy receives all traffic on ports 80 and 443 and sends each hostname to the right internal server.

Only ports 80 and 443 are opened to the internet, and internal servers and ports stay hidden.

Before you start

  • A domain whose DNS is hosted somewhere ACME supports. These steps use Cloudflare.
  • DNS A records for each hostname (e.g. cloud.example.com) pointing at your WAN IP.
  • A Cloudflare API token with Zone → DNS → Edit permission for the zone.
1

Move the pfSense web interface off port 443

HAProxy needs ports 80 and 443, so move the web interface out of the way first.

  1. Go to System → Advanced → Admin Access.
  2. Set TCP port to something else, e.g. 10443.
  3. Tick WebGUI redirect – Disable webConfigurator redirect rule (this frees port 80).
  4. Click Save, then reconnect at https://<pfsense-ip>:10443.
2

Install the packages

  1. Go to System → Package Manager → Available Packages.
  2. Search for acme and click Install, then Confirm.
  3. Search for haproxy and install it the same way.
3

Create a Let's Encrypt account key

  1. Go to Services → ACME Certificates → Account Keys → Add.
  2. Name: letsencrypt-prod
  3. ACME Server: Let's Encrypt Production ACME v2. Use the Staging server while testing to avoid Let's Encrypt's rate limits.
  4. E-Mail Address: an address that should receive expiry warnings.
  5. Click Create new account key, then Register ACME account key, then Save.
4

Request the certificate

  1. Go to Services → ACME Certificates → Certificates → Add.
  2. Name: wildcard-example-com. Status: Active. ACME Account: letsencrypt-prod.
  3. Under Domain SAN list, add two entries, both with Method: DNS-Cloudflare and your API token:
    • example.com
    • *.example.com (the wildcard covers every subdomain, so new services don't need new certificates)
  4. Under Actions list, add a Shell Command so HAProxy loads each renewed certificate:
    /usr/local/etc/rc.d/haproxy.sh restart
  5. Click Save, then click Issue/Renew. A green log output ending in Cert success means it worked.
  6. Go to Services → ACME Certificates → General settings, tick Enable Acme client renewal job and click Save.
5

Enable HAProxy

  1. Go to Services → HAProxy → Settings.
  2. Tick Enable HAProxy. Set Maximum connections to e.g. 1000.
  3. Click Save, then Apply Changes.
6

Add a backend (one per internal service)

  1. Go to Services → HAProxy → Backend → Add.
  2. Name: nextcloud
  3. Under Server list, add one row: Mode active, Name nextcloud1, Address 10.0.0.20, Port 80. Tick Encrypt (SSL) only if the internal server itself uses HTTPS.
  4. Health check method: HTTP, or none if the server doesn't answer plain health checks.
  5. Click Save.
7

Add the HTTPS frontend

  1. Go to Services → HAProxy → Frontend → Add.
  2. Name: https-in. Status: Active.
  3. External address: WAN address (IPv4), port 443, tick SSL Offloading.
  4. Type: http / https (offloading).
  5. Under Access Control lists, add Name is_nextcloud, Expression Host matches, Value cloud.example.com.
  6. Under Actions, add Use Backend, condition is_nextcloud, backend nextcloud.
  7. Under Advanced settings, tick Use "forwardfor" option so the internal server sees visitors' real IPs.
  8. Under SSL Offloading, choose the wildcard-example-com certificate.
  9. Click Save, then Apply Changes.

For each additional service, add a backend (step 6) plus one ACL and one action to this frontend.

8

Redirect HTTP to HTTPS

  1. Go to Frontend → Add. Name: http-in. External address: WAN address, port 80, without SSL Offloading. Type: http / https (offloading).
  2. Under Actions, add http-request redirect with rule scheme https and no condition.
  3. Click Save, then Apply Changes.
9

Allow the traffic through the firewall

  1. Go to Firewall → Rules → WAN → Add.
  2. Protocol TCP, Source any, Destination This Firewall (self), Destination port HTTPS (443).
  3. Add a second rule for HTTP (80), then click Apply Changes.
10

Reaching the services from inside the network

Internal clients that look up cloud.example.com get the public IP, which often fails from inside the network. To fix it:

  1. In both frontends, add a second External address: LAN address on the same port.
  2. Go to Services → DNS Resolver → Host Overrides and point each hostname at pfSense's LAN IP.

Check it works

  • Browse to https://cloud.example.com. The padlock should show a valid Let's Encrypt certificate.
  • Go to Status → HAProxy Stats. The backend server should be green (UP).
  • From outside the network: curl -I https://cloud.example.com

Troubleshooting

  • HAProxy won't start: "cannot bind socket" – something else still uses port 80 or 443. Recheck step 1.
  • 503 Service Unavailable – the backend is DOWN. Check the IP, the port, and the health check method.
  • Certificate won't issue – check the API token permissions and look at the log under Issue/Renew. Try the Staging server first.
  • Redirect loops – the application thinks it's on HTTP. Set its "trusted proxy" or "overwrite protocol" setting to HTTPS (e.g. overwriteprotocol in Nextcloud).