├── 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 | ![Port forwarding](./images/UniFi-Firewall_Security-Port_Forwarding.png) 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 | ![export keys](./images/haproxy_kodirsync_exports.png) 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 | ![frontend](./images/haproxy_fe.png) 561 | 562 | ![frontend](./images/haproxy_be.png) 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 | --------------------------------------------------------------------------------