Deploy HAProxy on PfSense to make your applications and services accessible externally

HAProxy is a lightweight yet powerful TCP/HTTP Load balancer which allows for high availability configurations between multiple webservers with added integration for ACL’s (Access Control Lists), and access to specific backend services.

Bryan Lam
10 min readOct 24, 2021

How does it work in our use case?

At a high level, we’ll be creating a NAT rule on our firewall to pass port 80 / 443 through to the virtual appliance running HAProxy.

Through the use of ACL’s (Access Control Lists), we can use host name matching to proxy requests through to different backends simultaneously while encrypting client requests with SSL via Lets Encrypt.

With the way we are configuring HAProxy, direct TCP connections over IP are rejected and will not be established unless there is an explicit host match via the ACL’s we setup.

You can further lockdown HAProxy by limiting source IP’s and connection requests, however this is not covered in this guide. If you are interested in securing your HAProxy setup I would highly recommend you take a gaze at the following guide.

Additionally in this guide, you will be able to achieve an A+ on SSL Labs by configuring a few extra options including ciphers, disabling old TLS versions etc.

1. Installing the packages

First, let’s start by installing the necessary packages to get this going.

  1. Expand System → Package Manager

2. Switch to the ‘Available Packages’ tab

3. Search and install the following packages:

HAProxy

ACME

2. Account Key Generation for Lets Encrypt SSL

Head to Services → ACME → Account Keys, then click on ‘Add’ to create a new key.

Following this, Complete the following fields for Certificate options then click ‘Create new account key’.

Name, Description, ACME Server (Select LE Production ACME v2), Email Address (Use your real email!)

After you click ‘Create new account key’, select ‘Register ACME Account Key’, then click save.

3. PASS TRAFFIC THROUGH TO HAPROXY

First, we must pass all traffic from port 80 and 443 through to our PFSense Appliance.

Create a NAT rule on your firewall to do so for all TCP traffic, such as below. You will need to leave the source on any, to allow for connections from Lets Encrypt verification servers.

You will also need to create a WAN Rule on the PfSense appliance to allow inbound TCP traffic on 443 to the firewall itself

4. CREATE YOUR DNS ENTRIES

Create a DNS A Record entry for the domain or subdomain you’d like to use to make your application accessible.

Point the record to the outgoing public IP of where your HAProxy instance is running.

If you are using looking to use a service such as Akami or Cloudflare which proxies content through 3rd party servers to provide L7 DDOS Protection, make sure you do not use the default Lets Encrypt verification method. This will fail as it utilises DVSNI challenges, an alternative method such as HTTP or DNS will need to be used.

For example, if you are running a UniFi Software Network controller you would like to make accessible externally, you could utilise

unifi.yourdomain.com A 3600 yourpublicIP

5. REQUESTING YOUR SSL CERTIFICATE FROM LET’S ENCRYPT

Go to Services –> ACME Certificates, then click on ‘Certificates’

Click on ‘Add’ to create a new Certificate

You should now configure the name, description and respective ACME account we created earlier. You can leave everything else as is until you get to ‘Domain SAN List’. This is where we will have to setup our certificate challenge method.

At ‘Domain SAN List’, ACME will provide you with multiple methods to perform domain verification before issuing you the SSL Certificate, this is a required part of the process and I would recommend you use DNS verification wherever possible.

At the dropdown, select ‘DNS-Manual’ as the Method.

Then, if you would like a wildcard certificate which covers all subdomains, prepend the following to your domain and put it into the ‘Domainname’ field.

*.

The star represents a wildcard which covers everything including your primary domain. So at the end, you will be entering the following into ‘Domainname’

*.yourdomain.com

Now scroll down to the bottom of the page and click save.

You should now be brought back to the Certificates page where you can see your newly created Certificate. Click ‘Issue’ to begin the verification phase of certificate issuance.

A green message will appear above your Certificates page with a TXT DNS Entry you need to put into your domain to verify it before your certificate is issued, it will look similar to below.

Add the TXT record to your domain, then click ‘Renew’ instead of ‘Issue’. It may take some time for DNS propagation.

Congratulations! You should now have a wildcard SSL Certificate issued via Lets Encrypt.

If you go into System –> Certificate Manager –> Certificates, you’ll be able to see and export your certificate if required.

Additionally, you will want to go into Services –> ACME –> Settings and enable the Cron entry to automatically renew your certificates for you

6. CONFIGURE HAPROXY BACKEND SERVER(S)

Now we are at the fun part!

HAProxy backends are essentially individual entries which specify what your internal servers are. These are linked to a Frontend which we will do through ACL’s to make your applications & services accessible externally.

Following this, we will configure a HTTPS frontend.

For the purpose of this tutorial, I will create two backend servers. One which already has a self-signed SSL Certificate which we access internally over port 443, and another unencrypted server which we access over port 80.

Let’s give these servers some identifiers to make it a bit easier.

Server A which is a NAS device, is located at internal IP 10.0.0.100. Server A gets accessed internally at https://10.0.0.100 (Port 443) and uses a self-signed SSL Certificate.

Server B is a web server which is not configured for HTTPS and is only accessible internally over port 80 (Unencrypted HTTP)

SERVER A BACKEND CONFIGURATION

First, create a new Backend server pool for Server A. To do this, go to Services –> HAProxy –> Backend, then click ‘Add’

Give your backend server a descriptive name so it is easily identifiable.

Then, at the Server list, click the blue arrow dropdown to add a new internal server.

We will now add in our server details

Name = Provide a descriptive name of your serveAddress = Internal IP of your serverPort = Port you normally use to access this server internallyEncrypt (SSL) = If you access via HTTPS:// internally, check this box.r

Since we are not configuring multiple servers in a high availability instance, you can scroll down after this and disable TCP health checks.

After this, click ‘Save’ and the backend server you just created will show up in grey.

The reason it shows up grey is because we have not linked it to a frontend configuration yet.

SERVER B BACKEND CONFIGURATION

To configure Server B, follow Server A configuration. The only difference will be below.

Since Server B does not serve content using SSL internally, we will set the port to 80 and uncheck the Encrypt (SSL Port)

7. CONFIGURE HAPROXY FRONTEND SERVER(S)

Create a new default HTTPS frontend at Services –> HAProxy –> Frontend, then click ‘Add’

Unlike the backend service configuration, we can decide to work with either one or multiple frontends. To simplify management, I like to put everything into a single frontend and use multiple ACL’s which appear much cleaner in my opinion.

Setup your external address as your WAN address on Port 443 with SSL Offloading checked.

If you are virtualising a PFSense which is sitting behind another firewall, your WAN address will be an internal address which we configured the NAT rule to forward to earlier on.

ACCESS CONTROL LISTS

ACL’s determine who gets access to what service on your frontend based on variables you can setup. If we want to give an external user access to Server A through hostname, we will create a new ACL entry similar to below.

To create an ACL entry, scroll down the page and click the blue arrow.

Where:

Name = Provide a descriptive namExpression = Host starts withValue = Domain or subdomain you configured with an A record earliere

ACL ACTIONS

It’s time to configure the action to take for each ACL entry.

Each entry should be connected to at least one action. In our case, the action will be to proxy the matched request to the respective backend of either ServerA or ServerB depending on which public domain they hit.

Action = Use BackendConditional ACL Name = The name you gave each ACL (This is case sensitive!!)d
Backend = The correct backend for each Server

Once this is complete, your frontend should look like below.

We are still not done yet as we have to setup the SSL offloading so the client <–> HAProxy communication is encrypted.

Select your Let’s Encrypt Certificate now under the ‘SSL Offloading’ section of the frontend.

You can now save your frontend configuration.

8. ENABLE AND TEST HAPROXY

Go to HAProxy settings and check ‘Enable HAProxy’, then setup the maximum connections by referring to the memory –> Connection table.

After this, click save. You are now done with the initial configuration and you should be able to access your services via the Reverse proxy!

Make sure to check the SSL Certificate issued to confirm that HAProxy is encrypting traffic using Let’s Encrypt.

Bonus: Get SSL A+ with SSL Labs

To further increase SSL security, we can make the following changes in HAProxy to get that sweet green badge

First:

Services → HAProxy → Settings

Max DH Size 4096

Second:

Go to edit your frontend and add the following options

a) Advanced Pass Thru

rspidel ^Server:.*$
rspadd Content-Security-Policy:\ default-src\ https:\ unsafe-inline\ unsafe-eval
rspadd X-Frame-Options:\ SAMEORIGIN
rspadd X-Content-Type-Options:\ nosniff
rspadd X-Xss-Protection:\ 1;\ mode=block
rspadd Strict-Transport-Security:\ max-age=31536000;includeSubDomains;preload
rspadd Referrer-Policy:\ no-referrer-when-downgrade

Third:

Tick the following boxes under SSL Offloading

Finally:

c) Add the following advanced SSL options under SSL Offloading

no-sslv3 force-tlsv12 ciphers DH+AES256:!aNULL

You will most likely need to add additional ciphers depending on client support if you receive an SSL mismatch error in your browser. If this is you, try the following configuration which is more eased.

no-sslv3 force-tlsv12 ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS:!3DES:!ECDH+AES128:!RSA+AES128:!DH+AES128

Now if you run a test at https://www.ssllabs.com/ you should receive an A+. Enjoy!

--

--