Repository navigation
HAProxy ssl_sni_matches/host_matches frontend ACLs are stored via the API but never rendered into haproxy.cfg, breaking SNI/Host-based routing #948
Description
Is your feature request related to existing pfSense functionality that is missing from the API? Please describe.
Yes — Services > HAProxy > Settings > Frontend > <edit> > Access Control Lists, expression types "SSL SNI matches" / "Host matches" (and presumably the sibling ssl_sni_contains/ssl_sni_starts_with/ssl_sni_ends_with/ssl_sni_regex/host_* variants, only the two above were directly tested). The REST API exposes the identical field (ha_acls[].expression on /api/v2/services/haproxy/frontend, and the dedicated /api/v2/services/haproxy/frontend/acl sub-resource) and accepts/stores these values without error — but this isn't a missing field, it's a field that's accepted and echoed back correctly by GET yet doesn't actually work.
Is your feature request related to a problem? Please describe.
I'm always frustrated when I configure a TCP-mode SNI-passthrough frontend (the standard pfSense-HAProxy recipe for routing multiple HTTPS backends off one WAN IP without terminating TLS on pfSense) entirely through the REST API, and it silently fails to actually work. Repro:
POST /api/v2/services/haproxy/frontend— TCP-mode frontend with a default backend.POST /api/v2/services/haproxy/frontend/acl—{"parent_id": <id>, "name": "my_acl", "expression": "ssl_sni_matches", "value": "example.com"}.POST /api/v2/services/haproxy/frontend/action—{"parent_id": <id>, "action": "use_backend", "acl": "my_acl", "backend": "other_backend"}.POST /api/v2/services/haproxy/apply— returns200/{"applied": false}, no error surfaced to the caller.- HAProxy actually fails to reload. The only place the failure shows up is
/var/log/system.log:The generated config contains the[ALERT] : config : parsing [.../haproxy.cfg:N] : error detected while parsing switching rule : no such ACL : 'my_acl'. [ALERT] : config : Fatal errors found in configuration fileuse_backend ... if my_aclswitching line but never theacl my_acl ...declaration it depends on — so the ACL is silently dropped somewhere between stored config and renderedhaproxy.cfg, only for these expression types. Asource_ip-expression ACL added the identical way (same frontend, same nested/sub-resource creation paths tried both ways) rendered and reloaded correctly, so this isn't a general "ACLs aren't wired up" issue.
Describe the solution you'd like
Fix whatever translates a stored ACL's expression enum value into the corresponding acl <name> <fetch> line in haproxy.cfg so it covers ssl_sni_matches/ssl_sni_contains/ssl_sni_starts_with/ssl_sni_ends_with/ssl_sni_regex and the host_* family the same way it already correctly covers source_ip and (presumably) other working expression types.
Describe alternatives you've considered
expression: "custom"with the raw HAProxy fetch syntax asvalue(e.g."req.ssl_sni -i example.com"for SNI,"hdr(host) -i example.com"for Host) — this renders correctly and is the workaround I'm using, but it means the friendlier typed expression options are unusable for the single most common HAProxy-on-pfSense use case (SNI/Host-based backend routing).
Additional context
- Versions: pfSense 2.9.0-RELEASE (FreeBSD 16.0-CURRENT), pfSense-pkg-RESTAPI 2.10_2, pfSense-pkg-haproxy 0.65.7 (haproxy 3.2.19).
- Reproduced on a stock install with only pfSense-pkg-haproxy installed via
POST /api/v2/system/package {"name": "pfSense-pkg-haproxy"}, no other HAProxy config present beforehand. - Tried both ACL-creation paths (inline
ha_acls/a_actionitemsarrays in the frontend's own POST/PATCH body, and the dedicated/frontend/acl+/frontend/actionsub-resource endpoints) — both produce the same broken render, andGETon the frontend correctly echoes the ACL back with the rightparent_ideither way, confirming it's stored correctly and the bug is specifically in the config-file renderer. - Happy to provide full request/response payloads and
system.logexcerpts on request.
Activity
jaredhendrickson13 commentedon Oct 20, 2026
I'm having some trouble reproducing this. Could you provide the full frontend configuration you're using when you encounter this? Feel free to scrub any sensitive or environment specific values.
This is strange behavior because the API itself actually doesn't render the haproxy.cfg file, it just calls the pfSense function that is responsible for rendering the HAProxy configuration from the pfSense configuration. It's the same function that triggers when you click on the "Apply changes" button in the UI. The full frontend configuration will help narrow it down.
Thanks!
antoniodargenio commentedon Oct 20, 2026
Thanks for looking into this! I re-ran it on a throwaway frontend today and found the cause.
Repro (frontend bound to 127.0.0.1:18443, backends
prod_https/deb1_httpsalready exist):Rendered section (
/var/etc/haproxy_test/haproxy.cfg),aclline missing:Log:
error detected while parsing switching rule : no such ACL : 'is_demo_www'.Cause: in
haproxy.incthessl_sni_*entries of$a_acltypeshave'mode' => 'https', and the renderer skips an ACL when$acltype['mode'] != strtolower($frontendtype)(line ~1067). In the package, a TCP-mode SSL frontend has internal typehttps("ssl / https (TCP mode)"), but the API'stypefield takestcp, and that value is what gets stored. Sotcpnever matcheshttpsand allssl_sni_*ACLs are silently filtered.source_iphas nomode, so it is unaffected, which matches my first report.Confirmed:
PATCH {"id":4,"type":"https"}is accepted by the API, and then the same ACL renders (acl is_demo_www req.ssl_sni -i demo-www.example.com) and HAProxy reloads fine. So the model'stypechoices (tcp/http) probably need to map to the package'shttps/httpvalues, or thetypeenum should exposehttps. I'm not sure which is intended, sincetcpalso seems to be what the GUI would show as "ssl/https (TCP mode)" — does the GUI storehttpsfor that option on your side?Versions: pfSense 2.9.0, pfSense-pkg-RESTAPI 2.10_2, pfSense-pkg-haproxy 0.65.7 (HAProxy 3.2.19).
jaredhendrickson13 commentedon Oct 23, 2026
Yeah the UI's descriptive values are a little confusing compared to values they store internally in the XML config, which is what the API uses.
httpinternallyhttpsinternallytcpinternallyI think a good adjustment here would be to add some extra validation to the ACL's
expressionfield to ensure the type is support for the frontend's configuredtype. I can get that worked into the next patch.Thanks!
antoniodargenio commentedon Oct 23, 2026
Thanks for the clear explanation of the UI vs. internal values, that settles my question, and adding validation of the ACL
expressionagainst the frontendtypesounds like exactly the right fix. For what it's worth, we're usingtype: httpsfor TCP-mode SSL frontends meanwhile and it works well. Great, responsive maintenance on this project, thank you!feat(HAProxyFrontendACL): add additional validation for supported `ex…