The Problem
Since recent versions of Home Assistant, accessing add-ons (ESPHome, File Editor, Studio Code Server, etc.) through an HTTPS reverse proxy can generate these errors in the browser console:
Refused to display 'https://homeassistant.domain.com/api/hassio_ingress/...'
in a frame because it set 'X-Frame-Options' to 'DENY'.
Sandbox access violation: Blocked a frame at "https://homeassistant.domain.com"
from accessing a frame at "https://homeassistant.domain.com".
The frame being accessed is sandboxed and lacks the "allow-same-origin" flag.
Add-ons refuse to display, leaving a blank page in the Home Assistant interface.
Architecture and Context
A typical infrastructure setup:
- HAProxy 2.8 as the main reverse proxy
- Home Assistant exposed via
homeassistant.domain.com - Strict security policy with hardened headers (HSTS, X-Frame-Options, CSP, etc.)
Typical HAProxy configuration with global security headers:
frontend https_frontend
bind *:443 ssl crt /etc/haproxy/certs/
# Security headers applied to all backends
http-response set-header X-Frame-Options DENY
http-response set-header X-Content-Type-Options nosniff
http-response set-header Strict-Transport-Security "max-age=63072000"
Diagnosis: Why Does It Block?
Understanding Home Assistant Ingress
Home Assistant uses an ingress mechanism to display add-ons directly in its web interface. Technically, add-ons are loaded in <iframe> elements via URLs like:
https://homeassistant.domain.com/api/hassio_ingress/{token}/
The X-Frame-Options Conflict
The X-Frame-Options: DENY header prevents any site from loading the page in an iframe, even from the same domain. This is protection against clickjacking attacks.
However, Home Assistant needs to load its own add-ons in iframes to function correctly.
Why SAMEORIGIN Isn't Always Enough
Even when using X-Frame-Options: SAMEORIGIN at the Home Assistant backend level, the frontend HAProxy header is applied last and overwrites the backend header.

The Complete Solution
Step 1: Conditionally Set X-Frame-Options in the Frontend
We need to prevent the frontend from applying X-Frame-Options: DENY for Home Assistant while maintaining it for other services.
The trap: you can't directly use an ACL based on hdr(host) in an http-response rule because request headers are no longer available during the response phase.
Solution: Use a Transaction Variable
frontend https_frontend
bind *:443 ssl crt /etc/haproxy/certs/
# Mark Home Assistant during the request phase
acl is_homeassistant hdr(host) -i homeassistant.domain.com
http-request set-var(txn.is_ha) int(1) if is_homeassistant
# Apply DENY only if NOT Home Assistant
http-response set-header X-Frame-Options DENY if !{ var(txn.is_ha) -m int 1 }
# Other security headers
http-response set-header Strict-Transport-Security "max-age=63072000"
http-response set-header X-Content-Type-Options nosniff
# Reverse proxy headers
http-request set-header X-Forwarded-Proto https
http-request set-header X-Forwarded-Host %[req.hdr(Host)]
# Routing
use_backend homeassistant_backend if is_homeassistant
Step 2: Force SAMEORIGIN in the Home Assistant Backend
Some ingress routes may send their own X-Frame-Options header. To ensure consistency, we remove then force SAMEORIGIN:
backend homeassistant_backend
description Home Assistant IoT Platform
balance roundrobin
# CRITICAL: Remove then force SAMEORIGIN
http-response del-header X-Frame-Options
http-response set-header X-Frame-Options SAMEORIGIN
# WebSocket support for add-ons
option http-server-close
option forwardfor
# Extended timeouts for long sessions
timeout server 600s
timeout tunnel 3600s
server has 192.168.1.100:8123 check
Step 3: Home Assistant Configuration
In configuration.yaml, declare HAProxy as a trusted proxy:
http:
use_x_forwarded_for: true
trusted_proxies:
- 192.168.1.10 # HAProxy server IP
- 127.0.0.1
ip_ban_enabled: true
login_attempts_threshold: 5
Restart Home Assistant: Settings → System → Restart
Validation
Command-Line Tests
# Home Assistant should return SAMEORIGIN (or no header)
curl -I https://homeassistant.domain.com/api/hassio_ingress/test | grep -i x-frame
# Expected result: x-frame-options: SAMEORIGIN
# Other services should return DENY
curl -I https://grafana.domain.com/ | grep -i x-frame
# Expected result: x-frame-options: DENY
Browser Testing
- Clear all browser cache (Ctrl+Shift+Del → Clear everything)
- Or test in private/incognito mode
- Access Home Assistant → Settings → Add-ons
- Open an add-on (ESPHome, File Editor, etc.)
Add-ons should now load correctly without frame errors.
Pitfalls to Avoid
1. Browser Cache
Security headers are heavily cached by browsers. Even after fixing the server-side configuration, the browser may continue to display errors for several minutes.
Solution: Always test in incognito/private mode during debugging, or clear the complete cache.
2. HAProxy Rule Order
HAProxy applies headers in order: frontend first, then backend. If the frontend forces DENY, the backend cannot override it.
Solution: Conditionally set headers at the frontend level using transaction variables.
3. Forgetting trusted_proxies
Without the trusted_proxies directive in Home Assistant, X-Forwarded-* headers are ignored and HA thinks it's running in direct HTTP mode, which can cause redirect issues.
4. Multiple is_homeassistant ACLs
I initially defined the is_homeassistant ACL in multiple frontends (stats, https). This duplication can cause warnings.
Solution: Define ACLs where they're used and avoid unnecessary definitions.
Performance and Monitoring
This configuration has zero impact on performance. Transaction variables are stored in memory during request processing and freed immediately after.
To monitor proper header application:
# From HAProxy stats
echo "show info" | socat stdio /var/run/haproxy/admin.sock
# Real-time logs
tail -f /var/log/haproxy.log | grep homeassistant

Conclusion
The conflict between strict security policies (X-Frame-Options: DENY) and Home Assistant's ingress mechanisms is a common problem in reverse proxy architectures.
The solution relies on three pillars:
- Intelligent conditioning of headers at the frontend level via transaction variables
- Explicit forcing of
SAMEORIGINat the backend level - Proper configuration of Home Assistant to accept proxied connections
This approach maintains maximum security for all other services while allowing Home Assistant to function correctly with its add-ons.