pfSense – HAProxy – Port 443

This guide was written using pfSense with package HAProxy Version 1.7.11

I finally figured out how to configure the HAProxy pfSense package to allow for incoming traffic on port 443. It took around a week’s worth of evenings to understand things just enough to get them working. Currently, I have a few web services running as well as my inbound VPN traffic for when I’m out and about.

I haven’t been able to get the SSH part of it working, but to be fair I only attempted to to use it once. I’ll update this post with the relevant information once I have time to get it working.

First, I should mention that I kept this guide open almost at all times. I doubt I could have figured this out without that blog post. While there are other tutorials out there that focus on similar setups, none of them seemed to go as in depth as this one. However, some of the information in that guide is outdated so I did my best to update the settings for the current package version.

The most important thing here is to go slowly. I followed that guide a few times and failed because I overlooked a small step.

Here’s an overview of what we want to do:

haproxy pfsense diagram

HAProxy Overview

DynamicDNS

Since I’m using a home internet connection, I don’t have a static IP address. I’ll need to configure pfSense to send it’s current IP address to a service so that the domain name always points to the right place.

Cloudflare

We will be using Cloudflare’s free package for DynamicDNS as well as for the Acme Package. I’m going to assume that you will already have a domain and will use it exclusively for the purposes listed here. I don’t recall changing any other settings except the ones listed below.

Main

  • +Add Site
    • Fill in domain name

DNS

  • DNS Records
    • A
    • *
    • Your Home IP
    • Automatic TTL
    • Add Record
  • DNS Records
    • A
    • *.ssh
    • Your Home IP
    • Automatic TTL
    • Add Record
  • DNS Records
    • A
    • *.vpn
    • Your Home IP
    • Automatic TTL
    • Add Record

Crypto

  • SSL
    • Off

pfSense

We need to configure pfSense to send the DynamicDNS info to Cloudflare.

Services > DynamicDNS > Click + Add
  • Service Type: Cloudflare
  • Interface to monitor: WAN
  • Hostname
    • Hostname: emby
    • Domain name: hostname.com
  • Username: email
  • Password: Cloudflare API Key
  • Description: emby.hostname.com

Do this for each subdomain you want to use including the *.vpn and *.ssh ones.

Generate Certificates

We will need to generate certificates from a trusted provider such as Let’s Encrypt and a few from within pfSense itself.

Wildcard Certificates

We will need 2 wildcard certificates from Let’s Encrypt or another certificate provider. Creating wildcard certificates will allow us to create subdomains without having to generate a new certificate for each one.

I’ll be using the Acme Package provided in pfSense.

Services > Acme Certificates > Account keys

Account Keys

  • Name: hostname
  • Description: hostname key
  • Acme Server: Let’s Encrypt Production ACME v2 (Applies rate limits to certificate requests)
  • E-Mail Address: youraddy@email.com
  • Account key: Create new account key
  • Acme account registration: Register acme account key
Services > Acme Certificates > Certificates

*.hostname.com

  • Name: Wildcard hostname
  • Description: hostname wildcard
  • Status: active
  • Acme Account: hostname
  • Private Key: 256bit ECDSA
  • Domain SAN list
    • Mode: Enabled
    • Domainname: *.hostname.com
    • Method: DNS-Cloudflare
    • Mode: Enabled
    • Domainname: hostname.com
    • Method: DNS-Cloudflare
    • Mode: Enabled

Then click Issue/Renew

*.vpn.hostname.com

  • Name: Wildcard vpn.hostname
  • Description: hostname vpn wildcard
  • Status: active
  • Acme Account: hostname
  • Private Key: 256bit ECDSA
  • Domain SAN list
    • Mode: Enabled
    • Domainname: *.vpn.hostname.com
    • Method: DNS-Cloudflare
    • Mode: Enabled
    • Domainname: hostname.com
    • Method: DNS-Cloudflare
    • Mode: Enabled

Then click Issue/Renew

Certificate Authorities

System > Cert. Manager > CAs

VPN CA

  • Descriptive name: Hostname VPN Remote Access
  • Method: Create an internal Certificate Authority
  • Key Length: 4096
  • Digest Algorithm: SHA512
  • Lifetime: 3650
  • Country code: US
  • State: OH
  • City: City
  • Organization: Hostname Inc.
  • Email address: security@hostname.com
  • Common Name: Hostname VPN Remote Access

SSLH CA

  • Descriptive name: Hostname SSLH Gateway
  • Method: Create an internal Certificate Authority
  • Key Length: 4096
  • Digest Algorithm: SHA512
  • Lifetime: 3650
  • Country code: US
  • State: OH
  • City: City
  • Organization: Hostname Inc.
  • Email address: security@hostname.com
  • Common Name: Hostname SSLH Gateway
ssl certificate authorities for haproxy

SSL Certificate Authorities for HAProxy

Internal Certificates

OpenVPN

  • Method: Create an internal Certificate
  • Descriptive name: vpn.hostname.com
  • Certificate authority: Hostname VPN Remote Access
  • Key Length: 4096
  • Digest Algorithm: SHA512
  • Certificate Type: Server Certificate
  • Lifetime: 3650
  • Country code: US
  • State: OH
  • City: City
  • Organization: Hostname Inc.
  • Email address: security@hostname.com
  • Common Name: vpn.example.com
  • Alternative Names: TYPE=DNS, VALUE=vpn.example.com

SSLH Gateway

  • Method: Create an internal Certificate
  • Descriptive name: ssh.hostname.com
  • Certificate authority: Hostname SSLH Gateway
  • Key Length: 4096
  • Digest Algorithm: SHA512
  • Certificate Type: Server Certificate
  • Lifetime: 3650
  • Country code: US
  • State: OH
  • City: City
  • Organization: Hostname Inc.
  • Email address: security@hostname.com
  • Common Name: ssh.hostname.com
  • Alternative Names: TYPE=DNS, VALUE=ssh.hostname.com

User certificates

Create user certificates for each user that will be allowed to access the vpn features.

System > Cert. Manager > Certificates

VPN User Certificate

  • Method: Create an internal Certificate
  • Descriptive Name: Hostname John Doe
  • Certificate authority: Hostname VPN Remote Access
  • Key length: 4096
  • Digest Algorithm: SHA512
  • Certificate Type: User Certificate
  • Lifetime: 3650
  • Country code: US
  • State: OH
  • City: City
  • Organization: Hostname Inc.
  • Email address: username@hostname.com
  • Common Name: Hostname John Doe
  • Alternative Names
    • TYPE=email, VALUE=john.doe@hostname.com
    • TYPE=email, VALUE=netmaster@hostname.com

From the blog I followed, he mentions that you can add other (pseudo/administrative) e-mail addresses here as an alias so you may even restrict access to certain pages/backends.

I did a little research and have yet to find out what conditions to use in the ACLs to allow this.

SSLH User Certificate

  • Method: Create an internal Certificate
  • Descriptive Name: Hostname-SSLH John Doe
  • Certificate authority: Hostname SSLH Gateway
  • Key length: 4096
  • Digest Algorithm: SHA512
  • Certificate Type: User Certificate
  • Lifetime: 3650
  • Country code: US
  • State: OH
  • City: City
  • Organization: Hostname Inc.
  • Email address: username@hostname.com
  • Common Name: Hostname-SSLH John Doe
  • Alternative Names
    • TYPE=email, VALUE=john.doe@hostname.com
    • TYPE=email, VALUE=hostmaster@hostname.com

Same info about the alternative names mentioned above applies here.

haproxy ssl certificates

Certificates we’ll use for HAProxy

OpenVPN

I won’t cover how to set up OpenVPN in this tutorial right now.

I recommend using THIS GUIDE to get a good start. Make sure to use the certificates generated above instead of the ones from the guide.

Main Config

  • Enabled:
  • Maximum Connections: 50
  • Global Advanced pass thru:
# set default parameters to the modern configuration
# using mozilla config generator: https://mozilla.github.io/server-side-tls/ssl-config-generator/
ssl-default-bind-ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256
ssl-default-bind-options no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
ssl-default-server-ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256
ssl-default-server-options no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets

# Time-to-first-Byte (TTFB) value needs to be optimized based on
# the actual public certificate chain see
# https://www.igvita.com/2013/10/24
# /optimizing-tls-record-size-and-buffering-latency/
tune.ssl.maxrecord 1370
haproxy settings

HAProxy Settings Page

Backend

We’re going to set up all of the backends first so we don’t have to switch back and forth between the frontend/backend tabs.

haproxy backend overview

Backend Overview

none

  • Name: none
  • Server list
    • Mode: disabled
    • Name: none
    • Forwardto: Address+Port:
    • Address: 127.0.0.1
    • Port: 80
    • Encrypt(SSL): no
    • SSL checks: no
haproxy backend none

Backend none

ssl-redirect

  • Name: ssl-redirect
  • Server list
    • Mode: interactive
    • Name: ssl-redirect
    • Forwardto: Address+Port:
    • Address: 127.0.0.1
    • Port: 8085
    • Encrypt(SSL): no
    • SSL checks: no
    • Backend pass thru
      • redirect scheme https code 301 if !{ ssl_fc }
haproxy ssl-redirect

Backend ssl-redirect

OpenVPN

  • Name: OpenVPN
  • Server list
    • Mode: active
    • Name: OpenVPN
    • Forwardto: Address+Port
    • Address: xx.xx.xx.xx (Your WAN IP Address)
    • Port: 1194
    • Encrypt(SSL): no
    • SSL checks: no
  • Connection timeout: 3000
  • Server timeout: 7200000
  • Retries: 2
  • Health check method: Basic
haproxy backend openvpn

Backend OpenVPN

WAN_HTTPS

  • Name: WAN_HTTPS
  • Server list
    • Mode: active
    • Name: WAN_HTTPS
    • Forwardto: Address+Port:
    • Address: 127.0.0.1
    • Port: 2043
    • Encrypt(SSL): no
    • SSL checks: no
  • Server timeout: 7200000
  • Health check method: None
  • Per server pass thru: send-proxy
haproxy backend wan_https

Backend WAN_HTTPS

WAN_AUTH

  • Name: WAN_AUTH
  • Server list
    • Mode: active
    • Name: WAN_AUTH
    • Forwardto: Address+Port:
    • Address: 127.0.0.1
    • Port: 2044
    • Encrypt(SSL): no
    • SSL checks: no
  • Server timeout: 7200000
  • Health check method: None
  • Per server pass thru: send-proxy
haproxy wan_auth

Backend WAN_AUTH

WAN_SSLH

  • Name: WAN_SSLH
  • Server list
    • Mode: active
    • Name: WAN_SSLH
    • Forwardto: Address+Port:
    • Address: 127.0.0.1
    • Port: 2022
    • Encrypt(SSL): no
    • SSL checks: no
  • Server timeout: 7200000
  • Health check method: None
  • Per server pass thru: send-proxy
haproxy backend wan_sslh

Backend WAN_SSLH

Emby

  • Name: Emby
  • Server list
    • Mode: active
    • Name: Emby
    • Forwardto: Address+Port:
    • Address: 192.168.xx.xx
    • Port: XXXX
    • Encrypt(SSL): No
    • SSL checks: no
  • Server timeout: 7200000
  • Health check method: HTTP
  • HSTS: 60 (Will change to 15768000)
  • Cookie protection:
haproxy backend emby

Backend Emby

Nextcloud

  • Name: Nextcloud
  • Server list
    • Mode: active
    • Name: Nextcloud
    • Forwardto: Address+Port:
    • Address: 192.168.xx.xx
    • Port: XXXX
    • Encrypt(SSL): Yes
    • SSL checks: no
  • Server timeout: 7200000
  • Health check method: HTTP
  • HSTS: 60 (Will change to 15768000)
  • Cookie protection:
haproxy backend nextcloud

Backend Nextcloud

Proxmox

  • Name: Proxmox
  • Server list
    • Mode: active
    • Name: Proxmox
    • Forwardto: Address+Port:
    • Address: 192.168.xx.xx
    • Port: XXXX
    • Encrypt(SSL): Yes
    • SSL checks: no
  • Server timeout: 7200000
  • Health checking
    • Health check method: HTTP
    • Http check method: GET
  • HSTS: 60 (Will change to 15768000)
  • Cookie protection:
haproxy backend proxmox

Backend Proxmox

FreeNAS

  • Name: FreeNAS
  • Server list
    • Mode: active
    • Name: FreeNAS
    • Forwardto: Address+Port:
    • Address: 192.168.xx.xx
    • Port: XXXX
    • Encrypt(SSL): Yes
    • SSL checks: no
  • Server timeout: 7200000
  • Health check method: HTTP
  • HSTS: 60 (Will change to 15768000)
  • Cookie protection:
haproxy backend freenas

Backend FreeNAS

Frontend

Now we can set up our frontends.

haproxy frontend overview

Frontend Overview

WAN_HTTP

  • Name: WAN_HTTP
  • Description: Redirect http to https
  • Status: Active
  • External Address
    • Listen Address: WAN Address (IPv4)
  • Type: http / https(offloading)
haproxy frontend wan_http

Frontend WAN_HTTP

WAN_443

  • Name: WAN_443
  • Description: Sharing Port 443
  • Status: Active
  • External Address
    • Listen address: WAN address (IPV4)
  • Port: 443
  • Type: tcp
  • Default Backend: none (the ‘none’ that we created)
  • Client timeout: 7200000
  • Advanced pass thru:
tcp-request inspect-delay 5s
tcp-request content accept if { req.ssl_hello_type 1 } or !{ req.ssl_hello_type 1 }
haproxy frontend wan_443

Frontend WAN_443

WAN_443_HTTPS

  • Name: WAN_443_HTTPS
  • Description: HTTPS
  • Status: Active
  • Shared Frontend:
  • Primary frontend: WAN_443-tcp
  • Access Control lists
    • Name: 443httpsACL
    • Expression: Custom ACL
    • Not: yes
    • Value: req.ssl_sni -m end -i .vpn.hostname.com
    • Name: 443httpsACL
    • Expression: Custom ACL
    • Not: yes
    • Value: req.ssl_sni -m end -i .ssh.hostname.com
  • Actions
    • Action: Use Backend
    • Condition acl names: 443httpsACL
    • backend: WAN_HTTPS
haproxy wan_443_https

Frontend WAN_443_HTTPS

WAN_443_OpenVPN

  • Name: WAN_443_OpenVPN
  • Description: OpenVPN
  • Status: Active
  • Shared Frontend:
  • Primary frontend: WAN_443-tcp
  • Default Backend: OpenVPN
haproxy frontend wan_443_ovpn

Frontend WAN_443_OVPN

WAN_443_AUTH

  • Name: WAN_443_AUTH
  • Description: *.vpn.hostname.com
  • Status: Active
  • Shared Frontend:
  • Primary frontend: WAN_443-tcp
  • Access Control lists
    • Name: 443authACL
    • Expression: Custom ACL
    • Not: no
    • Value: req.ssl_sni -m end -i .vpn.hostname.com
  • Actions
    • Action: Use Backend
    • Condition acl names: 443authACL
    • backend: WAN_AUTH
haproxy wan_443_auth

Frontend WAN_443_AUTH

WAN_443_SSLH

  • Name: WAN_443_SSLH
  • Description: *.ssh.hostname.com
  • Status: Active
  • Shared Frontend:
  • Primary frontend: WAN_443-tcp
  • Access Control lists
    • Name: 443sslhACL
    • Expression: Custom ACL
    • Not: no
    • Value: req.ssl_sni -m end -i .ssh.hostname.com
  • Actions
    • Action: Use Backend
    • Condition acl names: httpsACL
    • backend: WAN_SSLH
haproxy frontend wan_443_sslh

Frontend WAN_443_SSLH

WAN_AUTH

  • Name: WAN_HTTPS_auth
  • Description: *.vpn.hostname.com (HTTPS Reverse Proxy with X.509 Auth)
  • Status: Active
  • External Address
    • Listen address: localhost (IPV4)
    • Port: 2044
    • SSL Offloading:
    • Advanced: accept-proxy
  • Type: http / https(offloading)
  • Default Backend: none (the ‘none’ that we created)
  • Client timeout: 7200000
  • Use “forwardfor” option:
  • Advanced pass thru:
# Remove headers that expose security-sensitive information.
		rspidel ^Server:.*$
		rspidel ^X-Powered-By:.*$
		rspidel ^X-AspNet-Version:.*$

		# http-response set-header Content-Security-Policy script-src
		# Prevent clickjacking
		http-response set-header X-Frame-Options SAMEORIGIN
		# Prevent MIME Sniffing
		http-response set-header X-Content-Type-Options nosniff
		# X-XSS Protection
		http-response set-header X-XSS-Protection 1;mode=block
		# Prevent referrer on exit
		http-response set-header Referrer-Policy no-referrer-when-downgrade
  • SSL Offloading
    • Certificate: wildcard.vpn.hostname.com (CA:Acmecert: O=Let’s Encrypt, CN=Let’s Encrypt….)
    • Advanced ssl options: no-sslv3
  • SSL Offloading – client certificates
    • Client verification CA certificates: hostname VPN Remote Access
haproxy frontend wan_auth

Frontend WAN_AUTH

Proxmox

  • Name: Proxmox
  • Description: Proxmox
  • Status: Active
  • Shared Frontend: checked
  • Primary frontend: WAN_AUTH-http
  • Access Control lists:
    • Name: pxmxACL
    • Expression: Custom ACL
    • Not: no
    • Value: Host matches: proxmox.vpn.hostname.com
  • Actions:
    • Action: Use Backend
    • Condition acl names: pxmxACL
    • backend: Proxmox
haproxy frontend wan_auth proxmox

Frontend WAN_AUTH Proxmox

FreeNAS

  • Name: FreeNAS
  • Description: FreeNAS
  • Status: Active
  • Shared Frontend: checked
  • Primary frontend: WAN_AUTH-http
  • Access Control lists
    • Name: fnasACL
    • Expression: Custom ACL
    • Not: no
    • Value: Host matches: freenas.vpn.hostname.com
  • Actions
    • Action: Use Backend
    • Condition acl names: fnasACL
    • backend: FreeNAS
haproxy frontend wan_auth freenas

Frontend WAN_AUTH FreeNAS

WAN_SSLH

  • Name: WAN_SSLH
  • Description: *.ssh.hostname.com (SSL-secured SSH gateway with X.509 authentication)
  • Status: Active
  • External Address
    • Listen address: localhost (IPV4)
    • Port: 2022
    • SSL Offloading:
    • Advanced: accept-proxy
  • Type: ssl/https(TCP mode)
  • Default Backend: none (the ‘none’ that we created)
  • Client timeout: 7200000
  • SSL Offloading
    • Certificate: ssh.hostname.com (CA:Acmecert: O=Let’s Encrypt, CN=Let’s Encrypt….)
  • Advanced ssl options: no-sslv3
  • SSL Offloading – client certificates
    • Client verification CA certificates: hostname SSLH Gateway
haproxy wan_sslh settings

HAProxy WAN_SSLH Settings

WAN_HTTPS

  • Name: WAN_HTTPS
  • Description: HTTPS Reverse Proxy
  • Status: Active
  • External Address
    • Listen Address: localhost (IPv4)
    • Port: 2043
    • SSL Offloading:
    • Advanced: accept-proxy
  • Type: http / https(offloading)
  • Default Backend: none (the ‘none’ that we created)
  • Client timeout: 7200000
  • Use “forwardfor” option:
  • Advanced pass thru:
# Remove headers that expose security-sensitive information.
			rspidel ^Server:.*$
			rspidel ^X-Powered-By:.*$
			rspidel ^X-AspNet-Version:.*$

			# http-response set-header Content-Security-Policy "default-src http: https: data: unsafe-inline unsafe-eval*"
			# Prevent clickjacking
			http-response set-header X-Frame-Options SAMEORIGIN
			# Prevent MIME Sniffing
			http-response set-header X-Content-Type-Options nosniff
			# X-XSS Protection
			http-response set-header X-XSS-Protection 1;mode=block
			# Prevent referrer on exit
			http-response set-header Referrer-Policy no-referrer-when-downgrade
  • Certificate: wildcard.hostname.com (CA:Acmecert: O=Let’s Encrypt, CN=Let’s Encrypt….)
  • Advanced ssl options: no-sslv3
haproxy frontend wan_https

Frontend WAN_HTTPS

Emby

  • Name: Emby
  • Description: Emby
  • Status: Active
  • Shared Frontend:
  • Primary frontend: WAN_HTTPS-http
  • Access Control lists
    • Name: EmbyACL
    • Expression: Host matches:
    • Not: no
    • Value: emby.hostname.com
  • Actions
    • Action: Use Backend
    • Condition acl names: EmbyACL
    • backend: Emby
haproxy frontend wan_https emby

Frontend WAN_HTTPS Emby

Nextcloud

  • Name: Nextcloud
  • Description: Nextcloud
  • Status: Active
  • Shared Frontend:
  • Primary frontend: WAN_HTTPS-http
  • Access Control lists
    • Name: NxcdACL
    • Expression: Host matches:
    • Not: no
    • Value: nextcloud.hostname.com
  • Actions
    • Action: Use Backend
    • Condition acl names: NxcdACL
    • backend: Nextcloud
haproxy frontend wan_https nextcloud

Frontend WAN_HTTPS Nextcloud

Health Check

Here’s what my health check page looks like

haproxy stats page

HAProxy Stats Page

Generated Config File

Here is the output from the generated config file: GitLab Snippet

Connecting to the VPN Protected Sites

In pfSense, export the P12 user cert for yourself.

Import it into Firefox

Navigate to: Preferences > Privacy & Security > View Certificates > Your Certificates

Click Import. Select the .p12 file.

Now you should be able to access the sites you have configured for vpn access.

Final Thoughts

Things should be working now. If they’re not, open your config and compare it with mine to see what you may have missed. Double check everything. It’s easy to overlook a setting.

I was able to receive an A rating on https://securityheaders.com on my sites by tweaking the settings from what it recommended. This guide should get you pretty close.

If you cross reference this guide from the blog I linked, you may notice that he uses VALUE=req.ssl_hello_type 1 for some of the frontend ACLs. Each time that I tried it, I could not access the sites. Not sure why…

Feel free to comment on things that need imporovement or changed. Thanks for reading!

Helpful Links