HAproxy on PFsense (websocket working) Updated Dec24 - gaming09 - 2024-12-01
**This is an update to my original step by step, which is no longer valid on the last few versions of haproxy/pfsense
****Basic config and setup for HAproxy on Pfsense I suggest to follow Tom Lawrence's YT video here. The base config from that video will need to be in place before adding/editing my step by step
Overview Text Only:
Code: **Summary of HAProxy Configuration for WebSocket Support on pfSense (Obfuscated Version)**
Below is a detailed breakdown of the HAProxy configuration to get WebSocket connections working for your specific services like Jellyfin and Emby. This setup includes ACLs, actions, advanced settings, backend timeout settings, and backend passthrough settings.
### ACL Configuration (Access Control Lists)
To support WebSocket upgrades for the domains used, create the following ACLs:
1. **ACL Name: hdr\_connection\_upgrade**
- **Expression**: Custom ACL
- **Value**: `hdr(Connection) -i upgrade`
2. **ACL Name: hdr\_upgrade\_websocket**
- **Expression**: Custom ACL
- **Value**: `hdr(Upgrade) -i websocket`
3. **ACL Name: service1** (e.g., **jf** for Jellyfin)
- **Expression**: Host Matches
- **Value**: `service1.yourdomain.com`
4. **ACL Name: service2** (e.g., **emby** for Emby)
- **Expression**: Host Matches
- **Value**: `service2.yourdomain.com`
### Actions Configuration
Specify the actions to route the requests based on ACLs and headers:
1. **Set WebSocket Headers** (Place these before backend routing actions)
- **Action**: `http-request set-header`
- **Name**: `Connection`
- **Fmt**: `upgrade`
- **Condition ACL Names**: `{ req.hdr(Upgrade) -i websocket }`
- **Action**: `http-request set-header`
- **Name**: `Upgrade`
- **Fmt**: `%[req.hdr(Upgrade)]`
- **Condition ACL Names**: `{ req.hdr(Upgrade) -i websocket }`
2. **Use Backend (Routing based on ACLs)**
- **Backend: service1-backend** (e.g., **jellyfin-backend**)
- **Condition ACL Names**: `hdr_connection_upgrade hdr_upgrade_websocket or service1`
- **Backend: service2-backend** (e.g., **emby-backend**)
- **Condition ACL Names**: `hdr_connection_upgrade hdr_upgrade_websocket or service2`
### Advanced Settings for Frontend
To support stable WebSocket connections, configure advanced settings as follows:
1. **Client Timeout**: Set a longer client timeout to prevent premature disconnections.
- **Value**: `3600000` ms (1 hour)
2. **HTTP Close Option**: Set to `http-keep-alive (default)` to maintain persistent connections.
3. **Advanced Pass Thru** (Paste into the advanced pass-through field for the frontend):
```
http-request set-header X-Forwarded-Port %[dst_port]
http-request add-header X-Forwarded-Proto https if { ssl_fc }
http-response set-header Strict-Transport-Security max-age=31536000;includeSubDomains;preload
http-response set-header X-Frame-Options "SAMEORIGIN"
http-response set-header X-Content-Type-Options "nosniff"
http-response set-header Referrer-Policy no-referrer-when-downgrade
http-request set-header X-Real-IP %[src]
http-response set-header X-XSS-Protection "1; mode=block"
http-response set-header Feature-Policy "geolocation 'none'"
http-response set-header Permissions-Policy "geolocation=(), microphone=(), camera=()"
http-response set-header X-Download-Options "noopen"
http-request set-header Connection "upgrade" if { req.hdr(Upgrade) -i WebSocket }
http-request set-header Upgrade %[req.hdr(Upgrade)]
http-response set-header Connection "keep-alive"
http-response set-header Expect-CT "enforce, max-age=86400"
http-response del-header Server
http-response del-header X-Powered-By
option http-server-close
option forwardfor
```
### Backend Timeout and Retry Settings
Configure the backend with appropriate timeout values to handle long-running streams.
1. **Connection Timeout**: `60000` ms (1 minute)
2. **Server Timeout**: `3600000` ms (1 hour)
3. **Retries**: `5000` (Number of attempts to reconnect after failures)
### Backend Pass Through Settings
Paste the following configuration in the **Backend Pass Thru** box:
```
http-request set-header X-Forwarded-Port %[dst_port]
http-request add-header X-Forwarded-Proto https if { ssl_fc }
server service1 192.168.XXX.XXX:8096
http-response set-header Cache-Control "no-cache, no-store, must-revalidate"
http-response del-header Server
option http-server-close
option forwardfor
```
Examples and screenshots (screenshot is below example):
Settings Tab:
General Settings:
Max Connections 10000
Logging:
/var/run/log
local0
Debugging
Backend Tab:
Edit HAPRroxy Backend server pool:
Create your Backend
Name Jellyfin
Server: can be anything but needs to be consistent mine is 'jf' (lowercase) | include your address and port (default 8096) | No SSL | No SSL checks
Timeout / retry settings:
Connection timeout 60000
Server timeout 3600000
Retries 5000
Advanced Settings:
Backend pass thru (change 192.168.x.x to your JF IP) Code: http-request set-header X-Forwarded-Port %[dst_port]
http-request add-header X-Forwarded-Proto https if { ssl_fc }
server jf 192.168.X.X:8096
http-response set-header Cache-Control "no-cache, no-store, must-revalidate"
http-response del-header Server
option http-server-close
option forwardfor
Frontend Tab:
Edit HAProxy Frontend:Your listening address (should be wan or whatever gw you're using)Port 443 with SSL offloading checked
Access Control lists: (Order Matters)
Have these entries first
hdr_connection_upgrade | Custom acl: | hdr(Connection) -i upgrade
hdr_upgrade_websocket | Custom acl: | hdr(Upgrade) -i websocket
Then your host matches second
name jf (or whatever the backend server name was (not the list name) )
jf | host matches | jf.yourdomain.com
Actions: (Order Matters)
http-request header set | { req.hdr(Upgrade) -i websocket } - name: Connection
- fmt: upgrade
http-request header set | { req.hdr(Upgrade) -i websocket } - name: Upgrade
- fmt: %[req.hdr(Upgrade)]
Use Backend | jf hdr_connection_upgrade hdr_upgrade_websocket or jf
Advanced Settings:
Client timeout: 7200000
Advanced pass thru:
Code: http-request set-header X-Forwarded-Port %[dst_port]
http-request add-header X-Forwarded-Proto https if { ssl_fc }
http-response set-header Strict-Transport-Security max-age=31536000;includeSubDomains;preload
http-response set-header X-Frame-Options "SAMEORIGIN"
http-response set-header X-Content-Type-Options "nosniff"
http-response set-header Referrer-Policy no-referrer-when-downgrade
http-request set-header X-Real-IP %[src]
http-response set-header X-XSS-Protection "1; mode=block"
http-response set-header Feature-Policy "geolocation 'none'"
http-response set-header Permissions-Policy "geolocation=(), microphone=(), camera=()"
http-response set-header X-Download-Options "noopen"
http-request set-header Connection "upgrade" if { req.hdr(Upgrade) -i WebSocket }
http-request set-header Upgrade %[req.hdr(Upgrade)]
http-response set-header Connection "keep-alive"
http-response set-header Expect-CT "enforce, max-age=86400"
http-response del-header Server
http-response del-header X-Powered-By
option http-server-close
option forwardfor
Helpful additions internal DNS settings/config: This is assuming your using your pfsense for local DNSCode: Just an FYI if you have a TV or something static(non mobile) I wouldn't use the host name just use the IP. This routes the traffic through the pfsense box to your device instead of Server -> Device it becomes Server ->pfsense -> device
If you're using your hostname (jf.yourdomain.com) internally (you need to add your LAN address to your front end or have a separate fronted just for internal)
Frontend:
Go To Services>DNS Resolver:
Go to the bottom of the page to "Host Overides"
and click "Add" and fill in the following
https://i.imgur.com/Gf2YZWh.png
https://i.imgur.com/pQfevpI.png
click save add another entry for your jellyfin
for JF.YOURDOMAIN.com you would put it like this:
https://i.imgur.com/Vk99iY9.png
RE: HAproxy on PFsense (websocket working) Updated Dec24 - jaillybelly - 2024-12-11
Thanks for another fantastic guide gaming09! A question I have is: what HAproxy version is this guide appropriate for, and what versions was the previous guide made for? I noticed in your screenshots that you appear to be on HAproxy-devel and not stable HAproxy. The reason I ask is that I have been encountering various websocket errors in the logs that seem to coincide with playback stuttering. I realized I may be using the wrong guide for my HAproxy version but I don't know which one to use.
Thanks again for the incredibly detailed guide.
RE: HAproxy on PFsense (websocket working) Updated Dec24 - gaming09 - 2024-12-11
(2024-12-11, 05:05 AM)jaillybelly Wrote: Thanks for another fantastic guide gaming09! A question I have is: what HAproxy version is this guide appropriate for, and what versions was the previous guide made for? I noticed in your screenshots that you appear to be on HAproxy-devel and not stable HAproxy. The reason I ask is that I have been encountering various websocket errors in the logs that seem to coincide with playback stuttering. I realized I may be using the wrong guide for my HAproxy version but I don't know which one to use.
Thanks again for the incredibly detailed guide.
Hey, Im running 2.9.10, dlevel is haproxy-devel-2.9.d11 but this guide should work on both since they were merged a version or two back. What errors have you been getting.
RE: HAproxy on PFsense (websocket working) Updated Dec24 - jaillybelly - 2024-12-14
Great, thanks for filing that in gaming09. I've narrowed down the issue actually to a bug in the interaction between jellyfin and wireguard tunnels. Discussion is here: https://forum.jellyfin.org/t-websockets-dying-and-freezing-playback
|