├── images
├── haproxy_be.png
├── haproxy_fe.png
├── haproxy_kodirsync_exports.png
└── UniFi-Firewall_Security-Port_Forwarding.png
├── restore
└── haproxy.cfg
├── scripts
└── source
│ └── haproxy.cfg
└── README.md
/images/haproxy_be.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahuacate/pfsense-haproxy/HEAD/images/haproxy_be.png
--------------------------------------------------------------------------------
/images/haproxy_fe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahuacate/pfsense-haproxy/HEAD/images/haproxy_fe.png
--------------------------------------------------------------------------------
/images/haproxy_kodirsync_exports.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahuacate/pfsense-haproxy/HEAD/images/haproxy_kodirsync_exports.png
--------------------------------------------------------------------------------
/images/UniFi-Firewall_Security-Port_Forwarding.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahuacate/pfsense-haproxy/HEAD/images/UniFi-Firewall_Security-Port_Forwarding.png
--------------------------------------------------------------------------------
/restore/haproxy.cfg:
--------------------------------------------------------------------------------
1 | # Automaticaly generated, dont edit manually.
2 | # Generated on: 2024-08-04 23:54
3 | # Replace 'site1.foo.bar' with your domain
4 | # Replace 'site1\.foo\.bar' with your domain
5 | # Update CA paths to your CAs
6 | global
7 | maxconn 256
8 | log /var/run/log syslog info
9 | stats socket /tmp/haproxy.socket level admin expose-fd listeners
10 | uid 80
11 | gid 80
12 | nbthread 1
13 | hard-stop-after 15m
14 | chroot /tmp/haproxy_chroot
15 | daemon
16 | tune.ssl.default-dh-param 4096
17 | log-send-hostname HaproxyMasterNode
18 | server-state-file /tmp/haproxy_server_state
19 | ssl-default-bind-ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK
20 | tune.ssl.maxrecord 1370
21 | ssl-default-bind-options no-sslv3 no-tls-tickets
22 |
23 | listen HAProxyLocalStats
24 | bind 127.0.0.1:2200 name localstats
25 | mode http
26 | stats enable
27 | stats admin if TRUE
28 | stats show-legends
29 | stats uri /haproxy/haproxy_stats.php?haproxystats=1
30 | timeout client 5000
31 | timeout connect 5000
32 | timeout server 5000
33 |
34 | resolvers globalresolvers
35 | nameserver Unifi 192.168.1.5:53
36 | nameserver PiHole 192.168.1.6:53
37 | resolve_retries 3
38 | timeout retry 1s
39 | timeout resolve 10s
40 |
41 | frontend WAN_443-merged
42 | bind 192.168.2.1:443 name 192.168.2.1:443
43 | mode tcp
44 | log global
45 | timeout client 30000
46 | tcp-request inspect-delay 5s
47 | tcp-request content accept if { req.ssl_hello_type 1 } || !{ req.ssl_hello_type 1 }
48 | #tcp-request content accept if { req.ssl_hello_type 1 }
49 | acl acl1 req.ssl_hello_type 1
50 | acl acl2 req.ssl_sni -m end -i .sllh-site1.foo.bar
51 | acl acl3 req.ssl_sni -m end -i .vpn-site1.foo.bar
52 | acl acl req.ssl_sni -m end -i .sslh-site1.foo.bar
53 | acl acl req.ssl_sni -m end -i .vpn-site1.foo.bar
54 | acl acl2 req.len 0
55 | acl acl2 req.ssl_sni -m end -i .vpn-site1.foo.bar
56 | tcp-request content accept if acl1
57 | tcp-request content reject if acl2
58 | tcp-request content reject if acl3
59 | use_backend WAN_SSLH_ipvANY if acl
60 | use_backend OpenVPN_ipvANY if !acl1 !acl2
61 | use_backend WAN_HTTPS_auth_ipvANY if acl1 acl2
62 | default_backend DUMMY_BACKEND_ipvANY
63 | default_backend WAN_HTTPS_ipvANY
64 |
65 | frontend WAN_HTTPS-merged
66 | bind 127.0.0.1:2043 name 127.0.0.1:2043 ssl crt-list /var/etc/haproxy/WAN_HTTPS.crt_list accept-proxy alpn h2,http/1.1
67 | mode http
68 | log global
69 | option http-keep-alive
70 | option forwardfor
71 | acl https ssl_fc
72 | http-request set-header X-Forwarded-Proto http if !https
73 | http-request set-header X-Forwarded-Proto https if https
74 | timeout client 30000
75 | acl aclcrt_WAN_HTTPS var(txn.txnhost) -m reg -i ^audio-site1\.foo\.bar(:([0-9]){1,5})?$
76 | acl aclcrt_WAN_HTTPS var(txn.txnhost) -m reg -i ^guaca-site1\.foo\.bar(:([0-9]){1,5})?$
77 | acl aclcrt_WAN_HTTPS var(txn.txnhost) -m reg -i ^jellyfin-site1\.foo\.bar(:([0-9]){1,5})?$
78 | acl aclcrt_WAN_HTTPS var(txn.txnhost) -m reg -i ^testserver-site1\.foo\.bar(:([0-9]){1,5})?$
79 | acl jellyfin-acl var(txn.txnhost) -m str -i jellyfin-site1.foo.bar
80 | acl guacamole-acl var(txn.txnhost) -m str -i guaca-site1.foo.bar
81 | acl notifiarr-acl var(txn.txnhost) -m str -i notifiarr-site1.foo.bar
82 | acl testlab-acl var(txn.txnhost) -m str -i testlab-site1.foo.bar
83 | http-request set-var(txn.txnhost) hdr(host)
84 | use_backend jellyfin-server_ipvANY if jellyfin-acl
85 | use_backend guacamole-server_ipvANY if guacamole-acl
86 | use_backend notifiarr-server_ipvANY if notifiarr-acl
87 | use_backend testlab-server_ipvANY if testlab-acl
88 |
89 | frontend WAN_SSLH
90 | bind 127.0.0.1:2022 name 127.0.0.1:2022 no-sslv3 ssl crt-list /var/etc/haproxy/WAN_SSLH.crt_list ca-file /var/etc/haproxy/clientca_WAN_SSLH.pem verify required accept-proxy alpn ssh/2.0
91 | mode tcp
92 | log global
93 | maxconn 300
94 | timeout client 7200000
95 | acl acl-kodirsync ssl_fc_sni_reg -i kodirsync.sslh-site1.foo.bar
96 | use_backend kodirsync.sslh-site1.foo.bar_ipvANY if acl-kodirsync
97 |
98 | frontend WAN_HTTP
99 | bind 192.168.2.1:80 name 192.168.2.1:80
100 | mode http
101 | log global
102 | option http-keep-alive
103 | timeout client 30000
104 | default_backend SSL-redirect_ipvANY
105 |
106 | frontend WAN_HTTPS_auth
107 | bind 127.0.0.1:2044 name 127.0.0.1:2044 ssl crt-list /var/etc/haproxy/WAN_HTTPS_auth.crt_list ca-file /var/etc/haproxy/clientca_WAN_HTTPS_auth.pem verify required accept-proxy alpn h2,http/1.1
108 | mode http
109 | log global
110 | option http-keep-alive
111 | option forwardfor
112 | acl https ssl_fc
113 | http-request set-header X-Forwarded-Proto http if !https
114 | http-request set-header X-Forwarded-Proto https if https
115 | timeout client 30000
116 | acl aclcrt_WAN_HTTPS_auth var(txn.txnhost) -m reg -i ^sslh-site1\.foo\.bar(:([0-9]){1,5})?$
117 | http-request set-var(txn.txnhost) hdr(host)
118 |
119 | backend DUMMY_BACKEND_ipvANY
120 | mode tcp
121 | id 104
122 | log global
123 | timeout connect 30000
124 | timeout server 30000
125 | retries 3
126 | load-server-state-from-file global
127 | server none 127.0.0.1:80 id 105 disabled resolvers globalresolvers
128 |
129 | backend WAN_HTTPS_ipvANY
130 | mode tcp
131 | id 116
132 | log global
133 | timeout connect 30000
134 | timeout server 30000
135 | retries 3
136 | load-server-state-from-file global
137 | server wan_https 127.0.0.1:2043 id 117 check-ssl verify none resolvers globalresolvers send-proxy
138 |
139 | backend WAN_SSLH_ipvANY
140 | mode tcp
141 | id 120
142 | log global
143 | timeout connect 300000
144 | timeout server 30000
145 | retries 3
146 | load-server-state-from-file global
147 | server wan_sslh 127.0.0.1:2022 id 121 check-ssl verify none resolvers globalresolvers send-proxy
148 |
149 | backend OpenVPN_ipvANY
150 | mode tcp
151 | id 109
152 | log global
153 | timeout connect 30000
154 | timeout server 30000
155 | retries 2
156 | load-server-state-from-file global
157 | server openvpn 127.0.0.1:1194 id 110 resolvers globalresolvers
158 |
159 | backend WAN_HTTPS_auth_ipvANY
160 | mode tcp
161 | id 111
162 | log global
163 | timeout connect 30000
164 | timeout server 30000
165 | retries 3
166 | load-server-state-from-file global
167 | server wan_https_auth 127.0.0.1:2044 id 112 check-ssl verify none resolvers globalresolvers send-proxy
168 |
169 | backend jellyfin-server_ipvANY
170 | mode http
171 | id 106
172 | log global
173 | http-check send meth GET ver HTTP/1.1\r\nHost:\ jellyfin
174 | timeout connect 300000
175 | timeout server 300000
176 | retries 10
177 | load-server-state-from-file global
178 | option httpchk
179 | http-request set-header X-Forwarded-Port %[dst_port]
180 | http-request add-header X-Forwarded-Proto https if { ssl_fc }
181 | http-response set-header Cache-Control "no-cache, no-store, must-revalidate, private"
182 | http-response del-header Server
183 | server jellyfin-server 192.168.50.150:8096 id 122 check inter 5000 resolvers globalresolvers
184 |
185 | backend guacamole-server_ipvANY
186 | mode http
187 | id 101
188 | log global
189 | timeout connect 30000
190 | timeout server 30000
191 | retries 3
192 | load-server-state-from-file global
193 | server guacamole-server guacamole.local:8080 id 122 resolvers globalresolvers
194 |
195 | backend notifiarr-server_ipvANY
196 | mode http
197 | id 113
198 | log global
199 | timeout connect 30000
200 | timeout server 30000
201 | retries 3
202 | load-server-state-from-file global
203 | server notifiarr-server notifiarr.local:5454 id 122 resolvers globalresolvers
204 |
205 | backend testlab-server_ipvANY
206 | mode http
207 | id 100
208 | log global
209 | option log-health-checks
210 | timeout connect 30000
211 | timeout server 30000
212 | retries 3
213 | load-server-state-from-file global
214 | server testlab 192.168.1.190:80 id 114 resolvers globalresolvers
215 |
216 | backend kodirsync.sslh-site1.foo.bar_ipvANY
217 | mode tcp
218 | id 102
219 | log global
220 | timeout connect 30000
221 | timeout server 30000
222 | retries 30
223 | load-server-state-from-file global
224 | server kodirsync-server kodirsync.local:22 id 103 resolvers globalresolvers
225 |
226 | backend SSL-redirect_ipvANY
227 | mode http
228 | id 107
229 | log global
230 | timeout connect 30000
231 | timeout server 30000
232 | retries 3
233 | load-server-state-from-file global
234 | redirect scheme https code 301
--------------------------------------------------------------------------------
/scripts/source/haproxy.cfg:
--------------------------------------------------------------------------------
1 | # Automaticaly generated, dont edit manually.
2 | # Generated on: 2024-08-04 23:54
3 | # Replace 'site1.foo.bar' with your domain
4 | # Replace 'site1\.foo\.bar' with your domain
5 | # Update CA paths to your CAs
6 | global
7 | maxconn 256
8 | log /var/run/log syslog info
9 | stats socket /tmp/haproxy.socket level admin expose-fd listeners
10 | uid 80
11 | gid 80
12 | nbthread 1
13 | hard-stop-after 15m
14 | chroot /tmp/haproxy_chroot
15 | daemon
16 | tune.ssl.default-dh-param 4096
17 | log-send-hostname HaproxyMasterNode
18 | server-state-file /tmp/haproxy_server_state
19 | ssl-default-bind-ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK
20 | tune.ssl.maxrecord 1370
21 | ssl-default-bind-options no-sslv3 no-tls-tickets
22 |
23 | listen HAProxyLocalStats
24 | bind 127.0.0.1:2200 name localstats
25 | mode http
26 | stats enable
27 | stats admin if TRUE
28 | stats show-legends
29 | stats uri /haproxy/haproxy_stats.php?haproxystats=1
30 | timeout client 5000
31 | timeout connect 5000
32 | timeout server 5000
33 |
34 | resolvers globalresolvers
35 | nameserver Unifi 192.168.1.5:53
36 | nameserver PiHole 192.168.1.6:53
37 | resolve_retries 3
38 | timeout retry 1s
39 | timeout resolve 10s
40 |
41 | frontend WAN_443-merged
42 | bind 192.168.2.1:443 name 192.168.2.1:443
43 | mode tcp
44 | log global
45 | timeout client 30000
46 | tcp-request inspect-delay 5s
47 | tcp-request content accept if { req.ssl_hello_type 1 } || !{ req.ssl_hello_type 1 }
48 | #tcp-request content accept if { req.ssl_hello_type 1 }
49 | acl acl1 req.ssl_hello_type 1
50 | acl acl2 req.ssl_sni -m end -i .sllh-site1.foo.bar
51 | acl acl3 req.ssl_sni -m end -i .vpn-site1.foo.bar
52 | acl acl req.ssl_sni -m end -i .sslh-site1.foo.bar
53 | acl acl req.ssl_sni -m end -i .vpn-site1.foo.bar
54 | acl acl2 req.len 0
55 | acl acl2 req.ssl_sni -m end -i .vpn-site1.foo.bar
56 | tcp-request content accept if acl1
57 | tcp-request content reject if acl2
58 | tcp-request content reject if acl3
59 | use_backend WAN_SSLH_ipvANY if acl
60 | use_backend OpenVPN_ipvANY if !acl1 !acl2
61 | use_backend WAN_HTTPS_auth_ipvANY if acl1 acl2
62 | default_backend DUMMY_BACKEND_ipvANY
63 | default_backend WAN_HTTPS_ipvANY
64 |
65 | frontend WAN_HTTPS-merged
66 | bind 127.0.0.1:2043 name 127.0.0.1:2043 ssl crt-list /var/etc/haproxy/WAN_HTTPS.crt_list accept-proxy alpn h2,http/1.1
67 | mode http
68 | log global
69 | option http-keep-alive
70 | option forwardfor
71 | acl https ssl_fc
72 | http-request set-header X-Forwarded-Proto http if !https
73 | http-request set-header X-Forwarded-Proto https if https
74 | timeout client 30000
75 | acl aclcrt_WAN_HTTPS var(txn.txnhost) -m reg -i ^audio-site1\.foo\.bar(:([0-9]){1,5})?$
76 | acl aclcrt_WAN_HTTPS var(txn.txnhost) -m reg -i ^guaca-site1\.foo\.bar(:([0-9]){1,5})?$
77 | acl aclcrt_WAN_HTTPS var(txn.txnhost) -m reg -i ^jellyfin-site1\.foo\.bar(:([0-9]){1,5})?$
78 | acl aclcrt_WAN_HTTPS var(txn.txnhost) -m reg -i ^testserver-site1\.foo\.bar(:([0-9]){1,5})?$
79 | acl jellyfin-acl var(txn.txnhost) -m str -i jellyfin-site1.foo.bar
80 | acl guacamole-acl var(txn.txnhost) -m str -i guaca-site1.foo.bar
81 | acl notifiarr-acl var(txn.txnhost) -m str -i notifiarr-site1.foo.bar
82 | acl testlab-acl var(txn.txnhost) -m str -i testlab-site1.foo.bar
83 | http-request set-var(txn.txnhost) hdr(host)
84 | use_backend jellyfin-server_ipvANY if jellyfin-acl
85 | use_backend guacamole-server_ipvANY if guacamole-acl
86 | use_backend notifiarr-server_ipvANY if notifiarr-acl
87 | use_backend testlab-server_ipvANY if testlab-acl
88 |
89 | frontend WAN_SSLH
90 | bind 127.0.0.1:2022 name 127.0.0.1:2022 no-sslv3 ssl crt-list /var/etc/haproxy/WAN_SSLH.crt_list ca-file /var/etc/haproxy/clientca_WAN_SSLH.pem verify required accept-proxy alpn ssh/2.0
91 | mode tcp
92 | log global
93 | maxconn 300
94 | timeout client 7200000
95 | acl acl-kodirsync ssl_fc_sni_reg -i kodirsync.sslh-site1.foo.bar
96 | use_backend kodirsync.sslh-site1.foo.bar_ipvANY if acl-kodirsync
97 |
98 | frontend WAN_HTTP
99 | bind 192.168.2.1:80 name 192.168.2.1:80
100 | mode http
101 | log global
102 | option http-keep-alive
103 | timeout client 30000
104 | default_backend SSL-redirect_ipvANY
105 |
106 | frontend WAN_HTTPS_auth
107 | bind 127.0.0.1:2044 name 127.0.0.1:2044 ssl crt-list /var/etc/haproxy/WAN_HTTPS_auth.crt_list ca-file /var/etc/haproxy/clientca_WAN_HTTPS_auth.pem verify required accept-proxy alpn h2,http/1.1
108 | mode http
109 | log global
110 | option http-keep-alive
111 | option forwardfor
112 | acl https ssl_fc
113 | http-request set-header X-Forwarded-Proto http if !https
114 | http-request set-header X-Forwarded-Proto https if https
115 | timeout client 30000
116 | acl aclcrt_WAN_HTTPS_auth var(txn.txnhost) -m reg -i ^sslh-site1\.foo\.bar(:([0-9]){1,5})?$
117 | http-request set-var(txn.txnhost) hdr(host)
118 |
119 | backend DUMMY_BACKEND_ipvANY
120 | mode tcp
121 | id 104
122 | log global
123 | timeout connect 30000
124 | timeout server 30000
125 | retries 3
126 | load-server-state-from-file global
127 | server none 127.0.0.1:80 id 105 disabled resolvers globalresolvers
128 |
129 | backend WAN_HTTPS_ipvANY
130 | mode tcp
131 | id 116
132 | log global
133 | timeout connect 30000
134 | timeout server 30000
135 | retries 3
136 | load-server-state-from-file global
137 | server wan_https 127.0.0.1:2043 id 117 check-ssl verify none resolvers globalresolvers send-proxy
138 |
139 | backend WAN_SSLH_ipvANY
140 | mode tcp
141 | id 120
142 | log global
143 | timeout connect 300000
144 | timeout server 30000
145 | retries 3
146 | load-server-state-from-file global
147 | server wan_sslh 127.0.0.1:2022 id 121 check-ssl verify none resolvers globalresolvers send-proxy
148 |
149 | backend OpenVPN_ipvANY
150 | mode tcp
151 | id 109
152 | log global
153 | timeout connect 30000
154 | timeout server 30000
155 | retries 2
156 | load-server-state-from-file global
157 | server openvpn 127.0.0.1:1194 id 110 resolvers globalresolvers
158 |
159 | backend WAN_HTTPS_auth_ipvANY
160 | mode tcp
161 | id 111
162 | log global
163 | timeout connect 30000
164 | timeout server 30000
165 | retries 3
166 | load-server-state-from-file global
167 | server wan_https_auth 127.0.0.1:2044 id 112 check-ssl verify none resolvers globalresolvers send-proxy
168 |
169 | backend jellyfin-server_ipvANY
170 | mode http
171 | id 106
172 | log global
173 | http-check send meth GET ver HTTP/1.1\r\nHost:\ jellyfin
174 | timeout connect 300000
175 | timeout server 300000
176 | retries 10
177 | load-server-state-from-file global
178 | option httpchk
179 | http-request set-header X-Forwarded-Port %[dst_port]
180 | http-request add-header X-Forwarded-Proto https if { ssl_fc }
181 | http-response set-header Cache-Control "no-cache, no-store, must-revalidate, private"
182 | http-response del-header Server
183 | server jellyfin-server 192.168.50.150:8096 id 122 check inter 5000 resolvers globalresolvers
184 |
185 | backend guacamole-server_ipvANY
186 | mode http
187 | id 101
188 | log global
189 | timeout connect 30000
190 | timeout server 30000
191 | retries 3
192 | load-server-state-from-file global
193 | server guacamole-server guacamole.local:8080 id 122 resolvers globalresolvers
194 |
195 | backend notifiarr-server_ipvANY
196 | mode http
197 | id 113
198 | log global
199 | timeout connect 30000
200 | timeout server 30000
201 | retries 3
202 | load-server-state-from-file global
203 | server notifiarr-server notifiarr.local:5454 id 122 resolvers globalresolvers
204 |
205 | backend testlab-server_ipvANY
206 | mode http
207 | id 100
208 | log global
209 | option log-health-checks
210 | timeout connect 30000
211 | timeout server 30000
212 | retries 3
213 | load-server-state-from-file global
214 | server testlab 192.168.1.190:80 id 114 resolvers globalresolvers
215 |
216 | backend kodirsync.sslh-site1.foo.bar_ipvANY
217 | mode tcp
218 | id 102
219 | log global
220 | timeout connect 30000
221 | timeout server 30000
222 | retries 30
223 | load-server-state-from-file global
224 | server kodirsync-server kodirsync.local:22 id 103 resolvers globalresolvers
225 |
226 | backend SSL-redirect_ipvANY
227 | mode http
228 | id 107
229 | log global
230 | timeout connect 30000
231 | timeout server 30000
232 | retries 3
233 | load-server-state-from-file global
234 | redirect scheme https code 301
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
pfSense HAProxy
2 |
3 | This guide covers the use of the HAProxy add-on for pfSense.
4 |
5 | HAProxy is a reverse proxy server that operates behind a firewall within a private network. It directs client requests to the appropriate backend server, providing an extra layer of abstraction and control for efficient network traffic flow between clients and servers.
6 |
7 | To set up HAProxy, you can use the pfSense HAProxy add-on. With HAProxy, you can access your applications and internal servers through URLs like:
8 |
9 | - `https://unifi-site1.foo.bar` → `unifi.local`
10 | - `https://jellyfin-site1.foo.bar` → `jellyfin.local`
11 |
12 | By utilizing a single public-facing IP address and SSL port 443, you can:
13 |
14 | * Route HTTPS connections to a predefined list of backend servers, such as Jellyfin.
15 | * Direct SSH and Rsync connections to specific servers.
16 | * Enhance security by restricting login access based on client certificates.
17 |
18 | For SSH or Rsync connections, we use the TLS protocol and its SNI extension combined with the SSH ProxyCommand feature. This wraps connections with TLS and leverages SNI, allowing the client to specify the desired backend server.
19 |
20 | The pfSense package manager includes a pre-built distribution of HAProxy, making it readily available for installation.
21 |
22 | Prerequisites
23 |
24 | Read about our system-wide requirements before proceeding any further.
25 |
26 | - [ ] Proxmox hosts fully configured as per guide: [PVE Host Setup](https://github.com/ahuacate/pve-host) (Recommended)
27 | - [x] pfSense is fully configured as per guide: [pfSense Setup](https://github.com/ahuacate/pfsense-setup)
28 | - [x] PiHole DNS server
29 | - [x] You own a registered Domain name
30 |
31 |
32 |
33 | Table of Contents
34 |
35 |
36 |
37 | - [1. Create a Cloudflare Account](#1-create-a-cloudflare-account)
38 | - [2. Configure your domains at Cloudflare](#2-configure-your-domains-at-cloudflare)
39 | - [2.1. Create DNS A records for your servers](#21-create-dns-a-records-for-your-servers)
40 | - [2.2. Cloudflare SSL/TLS (formerly Crypto)](#22-cloudflare-ssltls-formerly-crypto)
41 | - [3. WAN Gateway Port Forwarding](#3-wan-gateway-port-forwarding)
42 | - [4. pfSense Dynamic DNS](#4-pfsense-dynamic-dns)
43 | - [4.1. Create pfSense Dynamic DNS entries](#41-create-pfsense-dynamic-dns-entries)
44 | - [5. Install ACME on pfSense](#5-install-acme-on-pfsense)
45 | - [5.1. ACME General Settings](#51-acme-general-settings)
46 | - [6. Generate ACME Certificates](#6-generate-acme-certificates)
47 | - [6.1. Create ACME Account Keys](#61-create-acme-account-keys)
48 | - [6.2. Create ACME Certificates - Media](#62-create-acme-certificates---media)
49 | - [6.3. Create ACME Certificates - SSLH and VPN](#63-create-acme-certificates---sslh-and-vpn)
50 | - [6.3.1. SSLH Server Certificate](#631-sslh-server-certificate)
51 | - [6.3.2. VPN Server Certificate](#632-vpn-server-certificate)
52 | - [7. Creating internal Certificate Authorities and certificates](#7-creating-internal-certificate-authorities-and-certificates)
53 | - [7.1. Create internal Certificate Authorities (CA's)](#71-create-internal-certificate-authorities-cas)
54 | - [7.1.1. Create internal certificate for SSLH](#711-create-internal-certificate-for-sslh)
55 | - [7.1.2. Create internal certificate for VPN](#712-create-internal-certificate-for-vpn)
56 | - [7.2. Create server certificates](#72-create-server-certificates)
57 | - [7.2.1. Create server certificate for SSLH](#721-create-server-certificate-for-sslh)
58 | - [7.2.2. Create server certificate for VPN](#722-create-server-certificate-for-vpn)
59 | - [7.3. Create user certificates](#73-create-user-certificates)
60 | - [7.3.1. Create user certificate for SSLH (single user certificate option)](#731-create-user-certificate-for-sslh-single-user-certificate-option)
61 | - [7.4. Export user CAs and certificates](#74-export-user-cas-and-certificates)
62 | - [8. Configure Firewall Rules](#8-configure-firewall-rules)
63 | - [9. Configure pfSense DNS Resolver Host Overrides](#9-configure-pfsense-dns-resolver-host-overrides)
64 | - [10. HAProxy Setup](#10-haproxy-setup)
65 | - [10.1. Install HAProxy](#101-install-haproxy)
66 | - [10.2. HAProxy General Settings](#102-haproxy-general-settings)
67 | - [10.3. HAProxy Frontend & Backend Settings](#103-haproxy-frontend--backend-settings)
68 | - [10.3.1. My Working haproxy.cfg](#1031-my-working-haproxycfg)
69 | - [11. Test your HAProxy server](#11-test-your-haproxy-server)
70 | - [11.1. HTTPS Test](#111-https-test)
71 | - [11.2. SSLH Test](#112-sslh-test)
72 | - [12. Patches & Fixes](#12-patches--fixes)
73 | - [12.1. Fix for pfSense Dynamic DNS](#121-fix-for-pfsense-dynamic-dns)
74 | - [12.1.1. Install a Cron Manager](#1211-install-a-cron-manager)
75 | - [12.1.2. Configure your Dynamic DNS Cron Schedule](#1212-configure-your-dynamic-dns-cron-schedule)
76 |
77 |
78 |
79 |
80 | # 1. Create a Cloudflare Account
81 | I suggest redirecting your domain's DNS Name Servers to Cloudflare for various benefits.
82 |
83 | Cloudflare offers fast DNS servers and supports an API Key that allows you to configure your pfSense DNS records. Additionally, they provide a free Dynamic DNS service, which can be particularly useful for basic home users. Cloudflare's DNS name server is free to use for these purposes.
84 |
85 | There are numerous tutorials available online that guide you through the process of transferring your DNS services from providers like Google and GoDaddy to Cloudflare.
86 |
87 | From this point forward, this tutorial will specifically refer to Cloudflare DNS management.
88 |
89 | # 2. Configure your domains at Cloudflare
90 | First, you must decide on your subdomain names. It’s part of the address used to direct traffic to a particular service running on your site's servers.
91 |
92 | For example, **jellyfin-`site1`.foo.bar** or **jellyfin-`site2`.foo.bar** where **`site1`** and **`site2`** are two different locations (i.e servers) in the world.
93 |
94 | ## 2.1. Create DNS A records for your servers
95 | First login to your Cloudflare Dashboard Home, choose your domain, and go to `DNS TAB`. You will be provided with a page to `Manage your Domain Name System (DNS) settings`. Using the Cloudflare web interface create the form entries you require by clicking `Add Record` after completing each entry as shown below (add whatever you want):
96 |
97 | | Type | Name | IPv4 address | Automatic TTL | Orange Cloud | Notes
98 | | :---: | :---: | :---: | :---: | :---: | :---
99 | | `A` | `sslh-site1` | 0.0.0.0 | `Automatic TTL` | `OFF` | *Note, Uncheck the cloudflare orange cloud for SSH (non-html).*
100 | | `A` | `vpn-site1` | 0.0.0.0 | `Automatic TTL` | `OFF` | *Note, Uncheck the cloudflare orange cloud for SSH (non-html).*
101 | | `A` | `jellyfin-site1` | 0.0.0.0 | `Automatic TTL` | `ON` |
102 | | `A` | `sample-site1` | 0.0.0.0 | `Automatic TTL` | `ON` | *Create for whatever HTTPS service you want - sonarr-site1, radarr-site1 etc.*
103 |
104 | The IPv4 address 0.0.0.0 will be updated by your pfSense DDNS service.
105 |
106 | ## 2.2. Cloudflare SSL/TLS (formerly Crypto)
107 | Using your Cloudflare Dashboard Home, choose your domain and go to `SSL/TLS TAB`. Using the Cloudflare web interface edit the following form entries to match the table below:
108 |
109 | | SSL/TLS | Value
110 | | :---: | :---
111 | | **Overview**
112 | | Your SSL/TLS encryption mode | `Full` |
113 | | **Edge Certificates**
114 | | Edge Certificates | Leave Default
115 | | Always Use HTTPS | `On`
116 | | HTTP Strict Transport Security (HSTS) | Leave Default
117 | | Minimum TLS Version | `TLS 1.0 (default)`
118 | | Opportunistic Encryption | `On`
119 | | TLS 1.3 | `On`
120 | | Automatic HTTPS Rewrites | `Off`
121 | | Certificate Transparency Monitoring | 'Off'
122 | | Disable Universal SSL | Leave Default (should be enabled)
123 | | **Origin Server**
124 | | Origin Certificates | Leave Default
125 | | Authenticated Origin Pulls | `Off`
126 | | **Custom Hostnames**
127 | | Custom Hostnames | Leave Default
128 |
129 | # 3. WAN Gateway Port Forwarding
130 | You must create Port Forward rules from your WAN Gateway device to your pfSense host. Apply the following rules to your WAN Gateway device (shown are UniFi USG rules).
131 |
132 | Navigate using the UniFi controller web interface to `Settings` > `Firewall & Security` > `Port Forwarding` and complete as follows.
133 |
134 | | Name | From | Port | Dest IP/Port | Enabled | WAN Interface
135 | | :---: | :---: | :---: | :---: | :---: | :---:
136 | | HAProxy | `*` | `80` | `192.168.2.1:80` | ✅ | `WAN`
137 | | HAProxy | `*` | `443` | `192.168.2.1:443` | ✅ | `WAN`
138 | | HAProxy | `*` | `8443` | `192.168.2.1:8443` | ✅ | `WAN`
139 |
140 | 
141 |
142 | # 4. pfSense Dynamic DNS
143 | Cloudflare provides you with an API key (called the Global API Key) which gives pfSense the rights to update your domain's DNS information. So have your Cloudflare Global API key ready by:
144 | * Log in to Cloudflare Account and go to your Profile;
145 | * Scroll down and View your **Global API Key**;
146 | * Complete the password challenge and note your key.
147 |
148 | ## 4.1. Create pfSense Dynamic DNS entries
149 | Configure for each HAProxy backend server you want access to (i.e sslh-site.foo.bar, jellyfin-site.foo.bar, sonarr-site.foo.bar etc).
150 |
151 | Navigate using the pfSense web interface to `Services` > `Dynamic DNS`. Click `Add` and fill out the necessary fields as follows.
152 |
153 | | Dynamic DNS Client | Value
154 | | :--- | :---
155 | | Disable | `☐`
156 | | Service Type | `Cloudflare`
157 | | Interface to monitor | `LAN`
158 | | Hostname | `sslh-site1` followed by `foo.bar`
159 | | Cloudflare Proxy | `☐` Enable Proxy (Only disable for SSH. Otherwise enable for all HTTP servers such sonarr-site1.foo.bar, jellyfin-site1.foo.bar)
160 | | Verbose logging | `☐` Enable verbose logging
161 | | Username | `Enter your email used with Cloudflare`
162 | | Password | `Enter the Global API Key or API token`
163 | | TTL | `900s`
164 | | Description | `sslh-site1.foo.bar`
165 |
166 | Note: Disable Cloudflare Proxy on sslh-site1.foo.bar and vpn-site1.foo.bar entries.
167 |
168 | And click `Save & Force Update`. Now repeat the above steps for all your Cloudflare DNS A-record entries and servers (i.e `radarr-site1`, `sonarr-site1`, `ssh-site1`, `nzbget-site1`, `deluge-site1`, `ombi-site1`, `syncthing-site1`, `vpn-site1` etc)
169 |
170 | Then check your Cloudflare DNS A-records you created should change from 0.0.0.0 to your WAN IP address.
171 |
172 | # 5. Install ACME on pfSense
173 | We need to install the ACME package on your pfSense. ACME is Automated Certificate Management Environment, for automated use of LetsEncrypt certificates.
174 |
175 | Navigate using the pfSense web interface to `System` > `Package Manager` > `Available Packages Tab` and search for `ACME`. Install the `ACME` package.
176 |
177 | ## 5.1. ACME General Settings
178 | Navigate using the pfSense web interface to `Service` > `ACME` > `Settings` > `General settings` and fill out the necessary fields as follows:
179 |
180 | | General Settings Tab | Value
181 | | :--- | :---
182 | | Cron Entry | `☑ Enable Acme client renewal job`
183 | | Write Certificates | `☑ Write ACME certificates to /conf/acme/`
184 |
185 | # 6. Generate ACME Certificates
186 | We will need to generate certificates from a trusted provider such as Let’s Encrypt.
187 |
188 | We can use the ACME Package provided in pfSense.
189 |
190 | ## 6.1. Create ACME Account Keys
191 | First, you need to create some account keys. LetsEncrypt is rate limited so you want to make sure that you have everything configured correctly before requesting a real cert. To help people test, LetsEncrypt provides a test service that you can use as you figure out your settings without bumping into the rate limit on the production servers. Certs obtained from test services cannot be used. You simply change the Cert from test to production and Issue/Renew again for a full working Cert.
192 |
193 | So we will create two Account Keys.
194 |
195 | Navigate using the pfSense web interface to `Services` > `Acme Certificates` > `Account Keys`. Click `Add` and fill out the necessary fields as follows:
196 |
197 | | Edit Certificate options | Value | Notes
198 | | :--- | :--- | :---
199 | | **Production Key**
200 | | Name | `site1.foo-production` | *For example, `site1.foo-production`*
201 | | Description | `site1.foo-production key` | *For example, `site1.foo-production key`*
202 | | Acme Server | `Let’s Encrypt Production ACME v2 (Applies rate limits to certificate requests)`
203 | | E-Mail Address | Enter your email address
204 | | Account key | `Create new account key` | *Click `Create new account key`*
205 | | Acme account registration | `Register acme account key` | *Click `Register acme account key`*
206 | | **Test Key**
207 | | Name | `site1.foo-test` | *For example, `site1.foo-test`*
208 | | Description | `site1.foo-test key` | *For example, `site1.foo-test key`*
209 | | Acme Server | `Let’s Encrypt Staging ACME v2 (for TESTING purposes)`
210 | | E-Mail Address | Enter your email address
211 | | Account key | `Create new account key` | *Click `Create new account key`*
212 | | Acme account registration | `Register acme account key` | *Click `Register acme account key`*
213 |
214 | It is really important that you choose the Staging ACME v2 server. Only the v2 will support wildcard domains.
215 |
216 | Now click the `+ Create new account key` button and wait for the box to fill in with a new RSA/ECDSA private key.
217 |
218 | Then click the `Register ACME Account key`. The little cog will spin and if it worked the cog will turn into a check.
219 |
220 | Finally, click `Save`.
221 |
222 | ## 6.2. Create ACME Certificates - Media
223 | Navigate using the pfSense web interface to `Services` > `Acme Certificates` > `Certificates`. Click `Add` and fill out the necessary fields as follows. Notice I have multiple entries in the Domain SAN List. This means the same certificate will be used for each server connection. In this example we will get a certificate that covers servers `jellyfin-site1`, `radarr-site1`, `sonarr-site1`, `nzbget-site1` and `deluge-site1` - basically all your media server connections.
224 |
225 | | Edit Certificate options | Value
226 | | :--- | :---
227 | | Name | `site1.foo.bar`
228 | | Description | `site1.foo.bar media SSL Certificate`
229 | | Status | `active`
230 | | Acme Account | `site1.foo-test`
231 | | Private Key | `384-bit ECDSA`
232 | | OCSP Must Staple | `☐ Add the OCSP Must Staple extension to the certificate`
233 | | **Entry 1 - Domain SAN list**
234 | | Mode |`Enabled`
235 | | Domainname `jellyfin-site1.foo.bar` | *One entry per Cloudflare DNS A-record*
236 | | Method | `DNS-Cloudflare`
237 | | Mode | `Enabled`
238 | | Key | Fill in the Cloudflare Global API Key
239 | | Email | Enter your email address
240 | | Enable DNS alias mode | Leave Blank
241 | | Enable DNS domain alias mode | `☐ (Optional) Uses the challenge domain alias value as --domain-alias instead in the acme.sh call`
242 | | **Entry 2 - Domain SAN list**
243 | | Mode |`Enabled`
244 | | Domainname `sample-site1.foo.bar` | *One entry per Cloudflare DNS A-record*
245 | | Method | `DNS-Cloudflare`
246 | | Mode | `Enabled`
247 | | Key | Fill in the Cloudflare Global API Key
248 | | Email | Enter your email address
249 | | Enable DNS alias mode | Leave Blank
250 | | Enable DNS domain alias mode | `☐ (Optional) Uses the challenge domain alias value as --domain-alias instead in the acme.sh call`
251 | | **Remaining fields**
252 | | DNS Sleep | `120`
253 | | Action List `Add` | `/etc/rc.restart_webgui`
254 | | Last renewal | Leave Blank
255 | | Certificate renewal after | `60`
256 |
257 | Then click `Save` followed by `Issue/Renew`. A review of the output will appear on the page and if successful you see a RSA key has been generated. The output should begin like this:
258 |
259 | ```
260 | site1.foo.bar
261 | Renewing certificate
262 | account: foo-test
263 | server: letsencrypt-staging-2
264 |
265 |
266 | /usr/local/pkg/acme/acme.sh --issue -d 'jellyfin-site1.foo.bar' --dns 'dns_cf' -d 'sonarr-site1.foo.bar' --dns 'dns_cf' -d 'radarr-site1.foo.bar' --dns 'dns_cf' -d 'nzbget-site1.foo.bar' --dns 'dns_cf' -d 'deluge-site1.foo.bar' --dns 'dns_cf' -d --home '/tmp/acme/site1.foo.bar/' --accountconf '/tmp/acme/site1.foo.bar/accountconf.conf' --force --reloadCmd '/tmp/acme/site1.foo.bar/reloadcmd.sh' --log-level 3 --log '/tmp/acme/site1.foo.bar/acme_issuecert.log'
267 |
268 | Array
269 | (
270 | [path] => /etc:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin/
271 | [PATH] => /etc:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin/
272 | [CF_Key] => 8XXXXXXXXXXXXXXXXXXXXXXXXXXXXX
273 | [CF_Email] => yourname@example.com
274 | )
275 | [Sat Aug 3 14:34:55 +07 2019] Registering account
276 | [Sat Aug 3 14:34:58 +07 2019] Already registered
277 | [Sat Aug 3 14:34:58 +07 2019] ACCOUNT_THUMBPRINT='XXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
278 | [Sat Aug 3 14:34:58 +07 2019] Multi domain='DNS:jellyfin-site1.foo.bar,DNS:sonarr-site1.foo.bar,radarr-site1.foo.bar,nzbget-site1.foo.bar,deluge-site1.foo.bar'
279 | [Sat Aug 3 14:34:58 +07 2019] Getting domain auth token for each domain
280 | [Sat Aug 3 14:35:12 +07 2019] Getting webroot for domain='jellyfin-site1.foo.bar'
281 | [Sat Aug 3 14:35:12 +07 2019] Getting webroot for domain='sonarr-site1.foo.bar'
282 | [Sat Aug 3 14:35:12 +07 2019] Getting webroot for domain='radarr-site1.foo.bar'
283 | [Sat Aug 3 14:35:12 +07 2019] Getting webroot for domain='nzbget-site1.foo.bar'
284 | [Sat Aug 3 14:35:12 +07 2019] Getting webroot for domain='deluge-site1.foo.bar'
285 | [Sat Aug 3 14:35:12 +07 2019] jellyfin-site1.foo.bar is already verified, skip dns-01.
286 | [Sat Aug 3 14:35:12 +07 2019] sonarr-site1.foo.bar is already verified, skip dns-01.
287 | [Sat Aug 3 14:35:12 +07 2019] radarr-site1.foo.bar is already verified, skip dns-01.
288 | [Sat Aug 3 14:35:12 +07 2019] nzbget-site1.foo.bar is already verified, skip dns-01.
289 | [Sat Aug 3 14:35:12 +07 2019] deluge-site1.foo.bar is already verified, skip dns-01.
290 | [Sat Aug 3 14:35:12 +07 2019] Verify finished, start to sign.
291 | [Sat Aug 3 14:35:12 +07 2019] Lets finalize the order, Le_OrderFinalize: https://acme-v02.api.letsencrypt.org/acme/finalize/XXXXXXXXXXXXXXXXXXXXX
292 | [Sat Aug 3 14:35:15 +07 2019] Download cert, Le_LinkCert: https://acme-v02.api.letsencrypt.org/acme/cert/XXXXXXXXXXXXXXXX
293 | [Sat Aug 3 14:35:18 +07 2019] Cert success
294 | -----BEGIN CERTIFICATE-----
295 | your key is here
296 | -----END CERTIFICATE-----
297 | ```
298 |
299 | Once you're satisfied everything is configured correctly, edit the certificate and change the Acme Account from `site1.foo-test` to `site1.foo-production` and repeat the `Issue/Renew` steps to generate a usable certificate.
300 |
301 | Final validation of your newly created LetsEncrypt certificate can be done by going to `System` > `Certificate Manager` > `Certificates`. It will show the issuer as something like **“Acmecert: 0=Let’s Encrypt,CN=Let’s Encrypt Authority X3,C=US”**.
302 |
303 | ## 6.3. Create ACME Certificates - SSLH and VPN
304 | This is a repeat of the previous step but for SSLH (SSH or Kodi-Rsync connections) and VPN connections.
305 |
306 | Navigate using the pfSense web interface to `Services` > `Acme Certificates` > `Certificates`. Click `Add` and fill out the necessary fields as follows. Notice I have multiple entries in the Domain SAN List. In this example, we will get a certificate that covers servers `sslh-site1` and `vpn-site1` only - all your secure private server connections.
307 |
308 | ### 6.3.1. SSLH Server Certificate
309 |
310 | | Edit Certificate options | Value
311 | | :--- | :---
312 | | Name | `sslh-site1.foo.bar`
313 | | Description | `sslh-site1.foo.bar media SSL Certificate`
314 | | Status | `active`
315 | | Acme Account | `site1.foo-test`
316 | | Private Key | `384-bit ECDSA`
317 | | OCSP Must Staple | `☐ Add the OCSP Must Staple extension to the certificate`
318 | | **Entry 1 - Domain SAN list**
319 | | Mode |`Enabled`
320 | | Domainname `sslh-site1.foo.bar` | *One entry per Cloudflare DNS A-record*
321 | | Method | `DNS-Cloudflare`
322 | | Mode | `Enabled`
323 | | Key | Fill in the Cloudflare Global API Key
324 | | Email | Enter your email address
325 | | Enable DNS alias mode | Leave Blank
326 | | Enable DNS domain alias mode | `☐ (Optional) Uses the challenge domain alias value as --domain-alias instead in the acme.sh call`
327 |
328 | ### 6.3.2. VPN Server Certificate
329 |
330 | | Edit Certificate options | Value
331 | | :--- | :---
332 | | Name | `vpn-site1.foo.bar`
333 | | Description | `vpn-site1.foo.bar media SSL Certificate`
334 | | Status | `active`
335 | | Acme Account | `site1.foo-test`
336 | | Private Key | `384-bit ECDSA`
337 | | OCSP Must Staple | `☐ Add the OCSP Must Staple extension to the certificate`
338 | | **Entry 1 - Domain SAN list**
339 | | Mode |`Enabled`
340 | | Domainname `vpn-site1.foo.bar` | *One entry per Cloudflare DNS A-record*
341 | | Method | `DNS-Cloudflare`
342 | | Mode | `Enabled`
343 | | Key | Fill in the Cloudflare Global API Key
344 | | Email | Enter your email address
345 | | Enable DNS alias mode | Leave Blank
346 | | Enable DNS domain alias mode | `☐ (Optional) Uses the challenge domain alias value as --domain-alias instead in the acme.sh call`
347 |
348 |
349 | # 7. Creating internal Certificate Authorities and certificates
350 | You need two separate certificate authentications to isolate HTTPS (LetsEncrypt), SSLH(for Kodi-Rsync), and VPN in HAProxy.
351 | 1. **Acmi SSLH - CA**
352 | All SSH traffic, including Kodi-Rsync, passes to the frontend SSL-secured SSH gateway (WAN_SSLH) with X.509 authentication. SSH traffic is captured and then managed by the HAProxy backends. You are using a kind of 2-factor-authentication (first X509 cert + username + password or SSH key - proxycommand) to log in which makes this a secure way to access any server in your internal LAN network from the outside world. You must take the following steps to create a secure SSH HAProxy managed access.
353 | 2. **Acmi VPN - CA**
354 | OpenVPN connect-in traffic.
355 |
356 | ## 7.1. Create internal Certificate Authorities (CA's)
357 | You need 2 separate internal Certificate Authorities (CA's) which we will be creating in the pfSense Certificate Manager.
358 |
359 | ### 7.1.1. Create internal certificate for SSLH
360 | Navigate using the pfSense web interface to `System` > `Certificate Manager` > `CAs`. Click `Add` and fill out the necessary fields as follows.
361 |
362 | | Create / Edit CA | Value
363 | | :--- | :---
364 | | Descriptive name | `sslh-site1.foo.bar`
365 | | Method | `Create a internal Certificate Authority`
366 | | Trust Store | ☑
367 | | Randomise Serial | ☑
368 | | **Internal Certificate Authority**
369 | | Key type | `ECDSA`
370 | | | `prime256v1 [HTTPS] [IPsec] [OpenVPN]`
371 | | Digest Algorithm | `sha512`
372 | | Lifetime (days) | `3650`
373 | | Common Name | `Acmi SSLH Access`
374 |
375 | And click `Save`.
376 |
377 | ### 7.1.2. Create internal certificate for VPN
378 | Navigate using the pfSense web interface to `System` > `Certificate Manager` > `CAs`. Click `Add` and fill out the necessary fields as follows.
379 |
380 | | Create / Edit CA | Value
381 | | :--- | :---
382 | | Descriptive name | `vpn-site1.foo.bar`
383 | | Method | `Create a internal Certificate Authority`
384 | | Trust Store | ☑
385 | | Randomise Serial | ☑
386 | | **Internal Certificate Authority**
387 | | Key type | `ECDSA`
388 | | | `prime256v1 [HTTPS] [IPsec] [OpenVPN]`
389 | | Digest Algorithm | `sha512`
390 | | Lifetime (days) | `3650`
391 | | Common Name | `Acmi VPN Access`
392 |
393 | And click `Save`.
394 |
395 | ## 7.2. Create server certificates
396 | You need to create two server certificates for Acmi SSLH Gateway and Acmi VPN Gateway.
397 |
398 | ### 7.2.1. Create server certificate for SSLH
399 | Navigate using the pfSense web interface to `System` > `Certificate Manager` > `Certificates`. Click `Add` and fill out the necessary fields as follows.
400 |
401 | | Create / Edit CA | Value
402 | | :--- | :---
403 | | Method | `Create a internal Certificate`
404 | | Descriptive name | `Acmi SSLH - Server`
405 | | **Internal Certificate**
406 | | Certificate authority | `Acmi SSLH - Server`
407 | | Key type | `ECDSA`
408 | | | `prime256v1 [HTTPS] [IPsec] [OpenVPN]`
409 | | Digest Algorithm | `sha512`
410 | | Lifetime (days) | `3650`
411 | | Common Name | `Acmi SSLH - Server`
412 | | **Certificate Attributes**
413 | | Certificate Type | `Server Certificate`
414 |
415 | And click `Save`.
416 |
417 | ### 7.2.2. Create server certificate for VPN
418 | Navigate using the pfSense web interface to `System` > `Certificate Manager` > `Certificates`. Click `Add` and fill out the necessary fields as follows.
419 |
420 | | Create / Edit CA | Value
421 | | :--- | :---
422 | | Method | `Create a internal Certificate`
423 | | Descriptive name | `Acmi VPN - Server`
424 | | **Internal Certificate**
425 | | Certificate authority | `Acmi VPN - Server`
426 | | Key type | `ECDSA`
427 | | | `prime256v1 [HTTPS] [IPsec] [OpenVPN]`
428 | | Digest Algorithm | `sha512`
429 | | Lifetime (days) | `3650`
430 | | Common Name | `Acmi VPN - Server`
431 | | **Certificate Attributes**
432 | | Certificate Type | `Server Certificate`
433 |
434 | And click `Save`.
435 |
436 | ## 7.3. Create user certificates
437 | Each user who needs to be authorized using VPN, HTTPS-auth secured backends or our SSLH Gateway will need to have user certificates being created by our internal pfSense CA’s.
438 |
439 | For VPN and HTTP-auth users, you can if you want create just one single user certificate for each SSH, Rsync or Kodi-Rsync server as each server should also have private key authorization. You may also issue certificates for each user if you want to create a complex multi authorization setup of your own.
440 |
441 | ### 7.3.1. Create user certificate for SSLH (single user certificate option)
442 | The following example is for user Kodirsync. Change the `Descriptive name`, `Common Name` and `Certificate authority` sections accordingly for VPN and SSH users.
443 |
444 | Navigate using the pfSense web interface to `System` > `Certificate Manager` > `Certificates`. Click `Add` and fill out the necessary fields as follows.
445 |
446 | | Create / Edit CA | Value
447 | | :--- | :---
448 | | Method | `Create a internal Certificate`
449 | | Descriptive name | `Acmi SSLH - Kodirsync`
450 | | **Internal Certificate**
451 | | Certificate authority | `Acmi VPN - CA`
452 | | Key type | `ECDSA`
453 | | | `prime256v1 [HTTPS] [IPsec] [OpenVPN]`
454 | | Digest Algorithm | `sha512`
455 | | Lifetime (days) | `3650`
456 | | Common Name | `Acmi SSLH - Kodirsync`
457 | | **Certificate Attributes**
458 | | Certificate Type | `User Certificate`
459 |
460 | And click `Save`.
461 |
462 | ## 7.4. Export user CAs and certificates
463 | Your SSH or Kodi-Rsync clients will require a certificate (.crt file) and certificate key (.key file):
464 | * **Acmi SSLH - Kodirsync** - CA file
465 | * **Acmi SSLH - Kodirsync** - key file
466 |
467 | Navigate using the pfSense web interface to `System` > `Certificate Manager` > `Certificates`. For user "Acmi SSLH - Kodirsync" click `Export Certificate` and `Export Key` and save to your local machine or NAS.
468 |
469 | 
470 |
471 | The two files will later be copied to your Kodi-Rsync server folder `~/.ssh` and renamed.
472 | > Acmi+SSLH+-+Kodirsync.crt >> sslh.crt
473 | > Acmi+SSLH+-+Kodirsync.key >> sslh-kodirsync.key
474 |
475 |
476 | # 8. Configure Firewall Rules
477 | You need to create two sets of firewall rules.
478 |
479 | * UniFi Firewall Rules
480 | * pfSense Firewall Rules
481 |
482 | Both sets of rules are clearly defined in our pfSense setup guide [here](https://github.com/ahuacate/pfsense-setup). This task must be done.
483 |
484 |
485 | # 9. Configure pfSense DNS Resolver Host Overrides
486 | PfSense should be set to use PiHole DNS and when PiHole is configured with "Conditional Forwarding" then DNS Resolver overrides are not required.
487 |
488 | If you are NOT using PiHole DNS configured with "Conditional Forwarding" then you may have issues resolving hostnames. The solution is to set "Host Overrides" for all backend servers.
489 |
490 | Enter individual HAProxy backend servers for which the pfSense DNS resolvers standard DNS should be overridden by specific IPv4/v6 addresses. This is mostly for SSLH backend servers.
491 |
492 | Now using the pfSense web interface go to `Services` > `DNS Resolver` > `General Settings` and scroll down to the section labelled `Host Overrides` amd create a new DNS rule that looks like the following example.
493 |
494 | | Host | Parent domain of host | IP to return for host | Description
495 | | :--- | :---: | :---: | :---:
496 | | jellyfin | local | 192.168.50.150 | Jellyfin server
497 | | kodirsync | local | 192.168.50.151 | Kodi-Rsync server
498 |
499 |
500 | # 10. HAProxy Setup
501 | HAProxy is a proxy for TCP and HTTP-based applications. You need to define frontends and backends.
502 |
503 | The HAProxy backend section defines a group of servers that are assigned to handle requests such as a Jellyfin or a Kodi-Rsync server. A backend server responds to incoming requests if a given condition is true.
504 |
505 | The HAProxy frontend section receives all of the incoming connection requests. All requests will be coming in one the same IP address and port (443) but we need a way to distinguish between requests so that those for jellyfin-site1.foo.bar go to the Jellyfin backend and those for sonarr-site1.foo.bar go to the Sonarr backend.
506 |
507 | ## 10.1. Install HAProxy
508 | Navigate using the pfSense web interface to `System` > `Package Manager` > `Available Packages Tab` and search for `haproxy-devel`. Install `haproxy-devel` package. The standard release version of HAProxy at version 0.61_1 didn't work with my HAProxy settings - maybe a later version will.
509 |
510 | ## 10.2. HAProxy General Settings
511 | Navigate using the pfSense web interface to `Service` > `HAProxy` > `Settings` and fill out the necessary fields as follows:
512 |
513 | | Settings Tab | Value
514 | | :--- | :---
515 | | Enable HAProxy | `☑` Enable HAProxy
516 | | Maximum connections | `256`
517 | | Number of processes to start | `1`
518 | | Number of threads to start per process | `1`
519 | | Reload behavior | `☑ Force immediate stop of old process on reload. (closes existing connections)`
520 | | Reload stop behavior | Leave default
521 | | Carp monitor | `Disabled`
522 | | **Stats tab, 'internal' stats port**
523 | | Internal stats port | `2200`
524 | | Internal stats refresh rate | Leave blank
525 | | Sticktable page refresh rate | Leave blank
526 | | **Logging**
527 | | Remote syslog host | `/var/run/log`
528 | | Syslog facility | `local0`
529 | | Syslog level | `Informational`
530 | | Log hostname | Leave blank
531 | | **Global DNS resolvers for haproxy**
532 | | DNS servers
533 | || `UniFi (router)` - `192.168.1.5` - `53`
534 | || `PiHole` - `192.168.1.6` - `53`
535 | | Retries | Leave blank
536 | | Retry timeout | Leave blank
537 | | Interval | Leave blank
538 | | **Global email notifications**
539 | | Mailer servers | Leave blank
540 | | Mail level | Leave blank
541 | | Mail myhostname | Leave blank
542 | | Mail from | Leave blank
543 | | Mail to | Leave blank
544 | | **Tuning**
545 | | SSL/TLS Compatibility Mode | `Auto`
546 | | Max SSL Diffie-Hellman size | `2048`
547 | | **Global Advanced pass thru**
548 | | Custom options
549 | | | `ssl-default-bind-ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK`
550 | | | `tune.ssl.maxrecord 1370`
551 | | | `ssl-default-bind-options no-sslv3 no-tls-tickets`
552 | | **Configuration synchronization**
553 | | HAProxy Sync | `☐ Sync HAProxy configuration to backup CARP members via XMLRPC.`
554 |
555 | And click `Save`.
556 |
557 | ## 10.3. HAProxy Frontend & Backend Settings
558 | Appreciation to this author for a excellent homelab HAProxy [solution](https://julian.pawlowski.me/geeking-out-with-haproxy-on-pfsense-the-ultimate/).
559 |
560 | 
561 |
562 | 
563 |
564 | ### 10.3.1. My Working haproxy.cfg
565 | This is my working `/var/etc/haproxy/haproxy.cfg` configuration file: [here](https://github.com/ahuacate/pfsense-haproxy/restore/haproxy.cfg). Change the address 'site1.foo.bar' to your sub-domain and domain.
566 |
567 | ```
568 | # Automaticaly generated, dont edit manually.
569 | # Generated on: 2022-12-07 22:20
570 | # Edit '-site1.foo.bar' to your sub/domain
571 | global
572 | maxconn 256
573 | log /var/run/log local0 info
574 | stats socket /tmp/haproxy.socket level admin expose-fd listeners
575 | uid 80
576 | gid 80
577 | nbthread 1
578 | hard-stop-after 15m
579 | chroot /tmp/haproxy_chroot
580 | daemon
581 | tune.ssl.default-dh-param 2048
582 | server-state-file /tmp/haproxy_server_state
583 | ssl-default-bind-ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK
584 | tune.ssl.maxrecord 1370
585 | ssl-default-bind-options no-sslv3 no-tls-tickets
586 |
587 | listen HAProxyLocalStats
588 | bind 127.0.0.1:2200 name localstats
589 | mode http
590 | stats enable
591 | stats admin if TRUE
592 | stats show-legends
593 | stats uri /haproxy/haproxy_stats.php?haproxystats=1
594 | timeout client 5000
595 | timeout connect 5000
596 | timeout server 5000
597 |
598 | resolvers globalresolvers
599 | nameserver Unifi 192.168.1.5:53
600 | nameserver PiHole 192.168.1.6:53
601 | resolve_retries 3
602 | timeout retry 1s
603 | timeout resolve 10s
604 |
605 | frontend WAN_443-merged
606 | bind 192.168.2.1:443 name 192.168.2.1:443
607 | mode tcp
608 | log global
609 | timeout client 30000
610 | tcp-request inspect-delay 5s
611 | tcp-request content accept if { req.ssl_hello_type 1 } || !{ req.ssl_hello_type 1 }
612 | #tcp-request content accept if { req.ssl_hello_type 1 }
613 | acl acl1 req.ssl_hello_type 1
614 | acl acl2 req.ssl_sni -m end -i .sllh-site1.foo.bar
615 | acl acl3 req.ssl_sni -m end -i .vpn-site1.foo.bar
616 | acl acl req.ssl_sni -m end -i .sslh-site1.foo.bar
617 | acl acl req.ssl_sni -m end -i .vpn-site1.foo.bar
618 | acl acl2 req.len 0
619 | acl acl2 req.ssl_sni -m end -i .vpn-site1.foo.bar
620 | tcp-request content accept if acl1
621 | tcp-request content reject if acl2
622 | tcp-request content reject if acl3
623 | use_backend WAN_SSLH_ipvANY if acl
624 | use_backend OpenVPN_ipvANY if !acl1 !acl2
625 | use_backend WAN_HTTPS_auth_ipvANY if acl1 acl2
626 | default_backend DUMMY_BACKEND_ipvANY
627 | default_backend WAN_HTTPS_ipvANY
628 |
629 | frontend WAN_HTTPS-merged
630 | bind 127.0.0.1:2043 name 127.0.0.1:2043 ssl crt-list /var/etc/haproxy/WAN_HTTPS.crt_list accept-proxy alpn h2,http/1.1
631 | mode http
632 | log global
633 | option http-keep-alive
634 | option forwardfor
635 | acl https ssl_fc
636 | http-request set-header X-Forwarded-Proto http if !https
637 | http-request set-header X-Forwarded-Proto https if https
638 | timeout client 30000
639 | acl aclcrt_WAN_HTTPS var(txn.txnhost) -m reg -i ^audio-site1\.foo\.bar(:([0-9]){1,5})?$
640 | acl aclcrt_WAN_HTTPS var(txn.txnhost) -m reg -i ^guaca-site1\.foo\.bar(:([0-9]){1,5})?$
641 | acl aclcrt_WAN_HTTPS var(txn.txnhost) -m reg -i ^jellyfin-site1\.foo\.bar(:([0-9]){1,5})?$
642 | acl jellyfin-acl var(txn.txnhost) -m str -i jellyfin-site1.foo.bar
643 | acl booksonic-acl var(txn.txnhost) -m beg -i audio-site1.foo.bar
644 | acl guacamole-acl var(txn.txnhost) -m str -i guaca-site1.foo.bar
645 | http-request set-var(txn.txnhost) hdr(host)
646 | use_backend jellyfin-server_ipvANY if jellyfin-acl
647 | use_backend booksonic-server_ipvANY if booksonic-acl
648 | use_backend guacamole-server_ipvANY if guacamole-acl
649 |
650 | frontend WAN_SSLH
651 | bind 127.0.0.1:2022 name 127.0.0.1:2022 no-sslv3 ssl crt-list /var/etc/haproxy/WAN_SSLH.crt_list ca-file /var/etc/haproxy/clientca_WAN_SSLH.pem verify required accept-proxy alpn ssh/2.0
652 | mode tcp
653 | log global
654 | maxconn 300
655 | timeout client 7200000
656 | acl acl-kodirsync ssl_fc_sni_reg -i kodirsync.sslh-site1.foo.bar
657 | use_backend kodirsync.sslh-site1.foo.bar_ipvANY if acl-kodirsync
658 |
659 | frontend WAN_HTTP
660 | bind 192.168.2.1:80 name 192.168.2.1:80
661 | mode http
662 | log global
663 | option http-keep-alive
664 | timeout client 30000
665 | default_backend SSL-redirect_ipvANY
666 |
667 | frontend WAN_HTTPS_auth
668 | bind 127.0.0.1:2044 name 127.0.0.1:2044 ssl crt-list /var/etc/haproxy/WAN_HTTPS_auth.crt_list ca-file /var/etc/haproxy/clientca_WAN_HTTPS_auth.pem verify required accept-proxy alpn h2,http/1.1
669 | mode http
670 | log global
671 | option http-keep-alive
672 | option forwardfor
673 | acl https ssl_fc
674 | http-request set-header X-Forwarded-Proto http if !https
675 | http-request set-header X-Forwarded-Proto https if https
676 | timeout client 30000
677 | acl aclcrt_WAN_HTTPS_auth var(txn.txnhost) -m reg -i ^sslh-site1\.foo\.bar(:([0-9]){1,5})?$
678 | http-request set-var(txn.txnhost) hdr(host)
679 |
680 | backend DUMMY_BACKEND_ipvANY
681 | mode tcp
682 | id 104
683 | log global
684 | timeout connect 30000
685 | timeout server 30000
686 | retries 3
687 | load-server-state-from-file global
688 | server none 127.0.0.1:80 id 105 disabled resolvers globalresolvers
689 |
690 | backend WAN_HTTPS_ipvANY
691 | mode tcp
692 | id 116
693 | log global
694 | timeout connect 30000
695 | timeout server 30000
696 | retries 3
697 | load-server-state-from-file global
698 | server wan_https 127.0.0.1:2043 id 117 check-ssl verify none resolvers globalresolvers send-proxy
699 |
700 | backend WAN_SSLH_ipvANY
701 | mode tcp
702 | id 120
703 | log global
704 | timeout connect 30000
705 | timeout server 30000
706 | retries 3
707 | load-server-state-from-file global
708 | server wan_sslh 127.0.0.1:2022 id 121 check-ssl verify none resolvers globalresolvers send-proxy
709 |
710 | backend OpenVPN_ipvANY
711 | mode tcp
712 | id 109
713 | log global
714 | timeout connect 30000
715 | timeout server 30000
716 | retries 2
717 | load-server-state-from-file global
718 | server openvpn 127.0.0.1:1194 id 110 resolvers globalresolvers
719 |
720 | backend WAN_HTTPS_auth_ipvANY
721 | mode tcp
722 | id 111
723 | log global
724 | timeout connect 30000
725 | timeout server 30000
726 | retries 3
727 | load-server-state-from-file global
728 | server wan_https_auth 127.0.0.1:2044 id 112 check-ssl verify none resolvers globalresolvers send-proxy
729 |
730 | backend jellyfin-server_ipvANY
731 | mode http
732 | id 106
733 | log global
734 | timeout connect 30000
735 | timeout server 30000
736 | retries 3
737 | load-server-state-from-file global
738 | server jellyfin-server jellyfin.local:8096 id 122 resolvers globalresolvers
739 |
740 | backend booksonic-server_ipvANY
741 | mode http
742 | id 100
743 | log global
744 | timeout connect 30000
745 | timeout server 30000
746 | retries 3
747 | load-server-state-from-file global
748 | server booksonic-server booksonic.local:4040 id 122 resolvers globalresolvers
749 |
750 | backend guacamole-server_ipvANY
751 | mode http
752 | id 101
753 | log global
754 | timeout connect 30000
755 | timeout server 30000
756 | retries 3
757 | load-server-state-from-file global
758 | server guacamole-server guacamole.local:8080 id 122 resolvers globalresolvers
759 |
760 | backend kodirsync.sslh-site1.foo.bar_ipvANY
761 | mode tcp
762 | id 102
763 | log global
764 | timeout connect 30000
765 | timeout server 30000
766 | retries 3
767 | load-server-state-from-file global
768 | server kodirsync-server kodirsync.local:22 id 103 resolvers globalresolvers
769 |
770 | backend SSL-redirect_ipvANY
771 | mode http
772 | id 107
773 | log global
774 | timeout connect 30000
775 | timeout server 30000
776 | retries 3
777 | load-server-state-from-file global
778 | redirect scheme https code 301
779 | ```
780 |
781 | # 11. Test your HAProxy server
782 | ## 11.1. HTTPS Test
783 | Use an external device such as your mobile phone and try connecting to your HTTPS backend server using a Web-Browser.
784 |
785 | ```
786 | https://jellyfin-site1.foo.bar
787 | ```
788 |
789 | ## 11.2. SSLH Test
790 | Use an external device such as your mobile phone (use the Termix App over 4G/5G network) and try connecting to your SSLH backend server. In this example, we connect to our SSLH Kodi-Rsync backend server using a SSH terminal command.
791 |
792 | ```
793 | ssh -vvvv -i ~/.ssh/username_kodirsync_id_ed25519 -o ProxyCommand="openssl s_client -quiet -connect sslh-site1.foo.bar:443 -servername kodirsync.sslh-site1.foo.bar -cert ~/.ssh/sslh.crt -key ~/.ssh/sslh-kodirsync.key" username_kodirsync@kodirsync.local -o StrictHostKeyChecking=no
794 | ```
795 |
796 | Remember the above CLI command requires all certs and keys to be available in the clients `~/.ssh` folder.
797 |
798 |
799 |
800 | # 12. Patches & Fixes
801 | Tweaks and fixes to make broken things work - sometimes :)
802 |
803 | ## 12.1. Fix for pfSense Dynamic DNS
804 | If your ISP frequently changes your WAN IP you may run into problems with out-of-date Cloudflare A-records pointing to an out-of-date IP address.
805 |
806 | It appears updates may take place if the WAN interface IP address changes, but not if the pfSense device is behind a router, gateway or firewall.
807 |
808 | You will know if you have a problem when you cannot remotely access your server node, the pfSense `Services` > `Dynamic DNS` > `Dynamic DNS Clients` page shows cached IP addresses in red indicating that pfSense knows the cached IP address is not the current public WAN IP and that has not updated the Dynamic DNS host (Cloudflare) with the current public WAN IP.
809 |
810 | The workaround is to install a CRON manager.
811 |
812 | ### 12.1.1. Install a Cron Manager
813 | Navigate using the pfSense web interface to `System` > `Package Manager` > `Available Packages Tab` and search for `Cron`. Install the `Cron` package.
814 |
815 | ### 12.1.2. Configure your Dynamic DNS Cron Schedule
816 | Navigate using the pfSense web interface to `Services` > `Cron` > `Settings Tab` and click on the pencil for entry with `rc.dyndns.update` in its command name. Edit the necessary fields as follows:
817 |
818 | | Add A Cron Schedule | Value
819 | | :--- | :---
820 | | Minute | `*/5`
821 | | Hour | `*`
822 | | Day of the Month | `*`
823 | | Month of the Year | `*`
824 | | Day of the Week | `*`
825 | | User | `root`
826 | | Command | Leave default
827 |
828 | And click `Save`.
829 |
830 | This will force pfSense to check for WAN IP changes every 5 minutes.
831 |
832 |
851 |
852 |
--------------------------------------------------------------------------------