Reverse proxy security and TLS requirements
This page is the checklist for whoever runs the reverse proxy or load balancer in front of an SSOGEN installation you operate yourself. Follow it top to bottom, then run the six checks in section 9.
The examples use the fictional company Acme Corp with SSOGEN at https://sso.acme.example. SSOGEN’s own port is 9941 unless you chose another at install time.
1. The two shapes
Section titled “1. The two shapes”SSOGEN always serves HTTPS on its own port, never plain HTTP. Browsers must reach the address of your installation over HTTPS.
| Shape | Address people use | Who holds the certificate browsers see |
|---|---|---|
| A. Direct. Browsers connect to SSOGEN’s own port. | https://sso.acme.example:9941 (the port is part of the address) |
SSOGEN. |
| B. Proxy or load balancer in front. It listens on 443 and connects to SSOGEN’s port over HTTPS. | https://sso.acme.example (no port) |
The proxy or load balancer. |
The address is fixed when SSOGEN is installed and cannot be changed afterwards, so choose the shape before you install. The rest of this page is about shape B, except section 3.
2. What the proxy must do, and must not do
Section titled “2. What the proxy must do, and must not do”| # | The proxy must | Why |
|---|---|---|
| 1 | Pass the Host header through unchanged (Apache: ProxyPreserveHost On; nginx: proxy_set_header Host $host;). |
SSOGEN answers only for its own address. A request for any other host is answered 421. SSOGEN never reads X-Forwarded-Host. |
| 2 | Connect to SSOGEN over HTTPS, with TLS 1.3 or TLS 1.2 and one of the cipher suites of section 4. | SSOGEN has no plain-HTTP port. A back-end TLS profile that offers only CBC or RSA key exchange fails the handshake, and the proxy answers 502 or 503. |
| 3 | Send the client’s address in one header and replace whatever the client sent in that header (section 5). | Sign-in policies, network zones, rate limits and the audit trail all use the client address. |
| 4 | Redirect HTTP to HTTPS itself. | Nothing in SSOGEN redirects port 80. |
| 5 | Accept request headers up to 96 KB (Apache: LimitRequestFieldSize 65536) and form bodies of at least 512 KB. |
Some sign-in requests are large; SSOGEN accepts form bodies up to 384 KB. |
| 6 | Allow 30 seconds or more for a response and for an idle connection; 45 seconds or more gives headroom. | A refused sign-in is held back on purpose (1.5 seconds by default, about 22 seconds in the worst case). The phone-approval screen asks about every 2 seconds. SSOGEN itself uses no WebSocket, streaming or long polling, so no buffering or upgrade settings are needed. |
| 7 | Leave the paths of section 7 reachable, and pass the Authorization header through unchanged. |
Web Agents, Gateways and provisioning clients call SSOGEN on their own paths. |
| 8 | Probe health correctly (section 8). | A probe with the wrong Host marks every node down. |
| # | The proxy must not | Why |
|---|---|---|
| 1 | Rewrite Host, or route by a header the client can set. |
Requests are refused with 421, or reach the wrong place. |
| 2 | Pass on the client’s own copy of the client-address header. | The recorded address would be whatever the client wrote. |
| 3 | Be listed more widely than it is. Never list 0.0.0.0/0 as a trusted proxy. |
Only your proxy’s own address or subnet belongs in the list. |
| 4 | Leave SSOGEN’s own port reachable from anywhere but the proxy. | A request that goes around the proxy also goes around everything the proxy does. Never publish that port to the internet. |
| 5 | Terminate TLS for Gateways (section 6). | A Gateway identifies itself in the TLS handshake. |
| 6 | Cache /logos/... beyond what SSOGEN’s Cache-Control header allows. |
SSOGEN decides who may see each image. |
Sticky sessions are not needed; a sticky setting does no harm.
3. Shape A: SSOGEN serves browsers directly
Section titled “3. Shape A: SSOGEN serves browsers directly”The installer creates a self-signed certificate for SSOGEN’s port. A browser that does not trust a self-signed certificate warns, so bring your own certificate for this shape:
--tls-cert /path/to/fullchain.pem --tls-key /path/to/privkey.pemAdd both options to the installer command you use. The certificate must cover the name in the address, the key must be its own key and must not be protected by a passphrase. Your PEM files are only read; SSOGEN keeps its own copy in its keystore.
A later --apply without the two options keeps your certificate untouched. To go back to a generated certificate, run --apply --use-generated-listener-cert. Open SSOGEN’s port to your users in the firewall.
4. TLS between the proxy and SSOGEN
Section titled “4. TLS between the proxy and SSOGEN”Protocols and ciphers SSOGEN accepts. No setting changes this list. SSOGEN refuses TLS 1.1 and older, CBC suites, DHE suites and RSA key exchange.
| Protocol | Cipher suites | Key-exchange groups |
|---|---|---|
| TLS 1.3 | Accepted | x25519, P-256, P-384 |
| TLS 1.2 | ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-RSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES256-GCM-SHA384, ECDHE-RSA-AES256-GCM-SHA384, ECDHE-ECDSA-CHACHA20-POLY1305, ECDHE-RSA-CHACHA20-POLY1305 |
x25519, P-256, P-384 |
Choose a back-end TLS profile on your load balancer that offers TLS 1.3 or TLS 1.2 with ECDHE and GCM, for the traffic and for the health check.
To see what the port accepts, run these on the SSOGEN server. The first two must connect; the last three must fail.
openssl s_client -connect 127.0.0.1:9941 -tls1_3 </dev/null 2>/dev/null | grep -E '^New|Protocol'openssl s_client -connect 127.0.0.1:9941 -tls1_2 -cipher ECDHE-RSA-AES128-GCM-SHA256 </dev/null 2>/dev/null | grep '^New'openssl s_client -connect 127.0.0.1:9941 -tls1_2 -cipher ECDHE-RSA-AES128-SHA </dev/nullopenssl s_client -connect 127.0.0.1:9941 -tls1_2 -cipher DHE-RSA-AES128-GCM-SHA256 </dev/nullopenssl s_client -connect 127.0.0.1:9941 -tls1_3 -groups ffdhe2048 </dev/nullThe certificate on SSOGEN’s port. In shape B the certificate your users’ browsers trust belongs on the proxy. SSOGEN’s own port uses the certificate the installer generated:
| Fact | Value |
|---|---|
| Type | Self-signed, RSA 3072, valid 20 years |
| Names it covers | The name in SSOGEN’s address, localhost, 127.0.0.1, ::1, and each name you gave with --listener-san |
| Public copy for your proxy | /etc/ssogen/listener.pem |
| Fingerprint | Printed by the installer’s --check, --plan and --apply |
| Reissue | Only with --renew-listener-cert, or by itself when it is within 30 days of its end or lacks a name you now ask for |
Pin that certificate on the proxy. For Apache on the same server:
SSLProxyEngine onSSLProxyVerify requireSSLProxyCheckPeerName onSSLProxyCheckPeerExpire onSSLProxyCACertificateFile /etc/ssogen/listener.pem
ProxyPreserveHost OnProxyPass / https://127.0.0.1:9941/ retry=0 connectiontimeout=5 timeout=30ProxyPassReverse / https://127.0.0.1:9941/For a proxy or load balancer on another host:
- When you install SSOGEN, add
--listener-san <name>for each name or address the proxy uses to connect. The address the proxy connects to must be a name on the certificate. - Copy
/etc/ssogen/listener.pemto the proxy and compare its SHA-256 fingerprint with the one the installer printed. - Configure the proxy to trust exactly that certificate for its connection to SSOGEN.
- After the certificate is reissued, copy the file again. On Apache, reload with
apachectl graceful. A proxy that still pins the old certificate reports a TLS error or answers 502.
If your load balancer cannot validate a back-end certificate, it can still connect, but it then cannot tell SSOGEN from something else answering on that address. Keep the link between the two on a private network you control, bind SSOGEN to one address, and block SSOGEN’s port from everywhere else with the firewall.
Bind and firewall SSOGEN’s port. With a proxy on the same server, install with --listen-address 127.0.0.1 and confirm that the port shows 127.0.0.1 only:
ss -ltnWith a proxy on another host, open the port to the proxy’s address only.
On the proxy host with SELinux enforcing, Apache needs permission to connect out:
setsebool -P httpd_can_network_connect onsemanage port -a -t http_port_t -p tcp <port>5. The client address
Section titled “5. The client address”SSOGEN must be told which addresses are your proxies and which header carries the client’s address. Three settings do that. Give them to the installer, or set them in /etc/ssogen/ssogen-platform.env and restart; they are read at start.
| Setting | Installer option | Meaning | Default |
|---|---|---|---|
SSOGEN_TRUSTED_PROXY_CIDRS |
--trusted-proxy (repeatable) |
The addresses or subnets SSOGEN sees your proxy connect from, as address/prefix. No host names. | Empty: no proxy; the connection’s own address is the client. |
SSOGEN_TRUSTED_PROXY_HOPS |
--trusted-proxy-hops N |
How many proxies add to the header between the browser and SSOGEN: 1 to 8. A value above 1 works only with X-Forwarded-For or Forwarded. |
1 |
SSOGEN_CLIENT_IP_HEADER |
--client-ip-header NAME |
The header your proxy writes: X-Forwarded-For, Forwarded, X-Real-IP, CF-Connecting-IP, True-Client-IP or X-Client-IP. |
X-Forwarded-For |
sudo systemctl restart ssogenRules for the list: a private range may be as wide as its block; any other range may be at most a /12 (IPv4) or a /28 (IPv6). Two short forms exist: --trusted-proxy private (the three private IPv4 blocks of RFC 1918 and the IPv6 unique local block fc00::/7) and --trusted-proxy google-cloud (130.211.0.0/22,35.191.0.0/16).
What to set for your chain
Section titled “What to set for your chain”| In front of SSOGEN | Hops | Trusted proxies |
|---|---|---|
| Apache httpd or nginx, on the same server or another | 1 | The web server’s address (127.0.0.1/32 on the same server) |
| One load balancer that adds the header (AWS Application Load Balancer, Azure Application Gateway, HAProxy, Traefik, Envoy) | 1 | The balancer’s address or subnets |
| A Google Cloud external Application Load Balancer | 1 | --trusted-proxy google-cloud |
| A CDN, then a load balancer that adds the header | 2 | The load balancer’s address or subnets and the CDN’s published ranges |
| A load balancer, then Apache or nginx on the SSOGEN server, both adding the header | 2 | The web server’s address and the load balancer’s address or subnets |
| A layer-4 load balancer that preserves the client address | Leave empty | Leave empty |
| A front end that neither keeps the client address nor adds a header | Not supported. Make it send the header, then set 1 and the front end’s address. |
What to set on the proxy
Section titled “What to set on the proxy”Product settings are as the vendors publish them; check the names in your version’s manual.
| Product | Set on it | On SSOGEN |
|---|---|---|
| Apache httpd | RequestHeader unset X-Forwarded-For early and ProxyPreserveHost On |
Apache’s address |
| nginx | proxy_set_header X-Forwarded-For $remote_addr; and proxy_set_header Host $host; |
nginx’s address |
| HAProxy | http-request set-header X-Forwarded-For %[src], or option forwardfor |
HAProxy’s address |
| AWS Application Load Balancer | Keep the X-Forwarded-For processing mode “append” |
The subnets the balancer is placed in |
| AWS Network Load Balancer | Turn client IP preservation on for the target group. SSOGEN does not read PROXY protocol. | Nothing: do not list the balancer |
| Azure Application Gateway | Nothing extra | The gateway’s subnet |
| Azure Front Door | Restrict the origin to your own Front Door profile | Front Door’s published egress ranges (service tag AzureFrontDoor.Backend); with a gateway behind it, that subnet too and hops 2 |
| F5 BIG-IP (terminating TLS) | Enable “Insert X-Forwarded-For” in the HTTP profile | The SNAT pool or self-IP subnet |
| Cloudflare | Allow the proxy’s port from Cloudflare’s published ranges only | Cloudflare’s ranges, and SSOGEN_CLIENT_IP_HEADER=CF-Connecting-IP |
| Traefik | Leave forwardedHeaders.insecure off |
Traefik’s address |
| Envoy | use_remote_address: true on the outermost Envoy |
Envoy’s address |
When a load balancer’s addresses change, trust the subnet it is placed in, not its current addresses. If it moves to a new subnet, add the new subnet to SSOGEN_TRUSTED_PROXY_CIDRS and to the firewall before the move, and restart SSOGEN.
Where to see the result. In the Admin Console, System health has a Client address card under Configuration that shows what is set and warns when the settings do not match the traffic. Network zones depend on these settings: see Network zones.
6. Gateways, Web Agents and client certificates
Section titled “6. Gateways, Web Agents and client certificates”| Caller | Through a proxy that terminates TLS? | What to do |
|---|---|---|
| Web Agent and Java agent | Yes. They send their agent key in the Authorization header. |
Do not block /agent/..., and pass Authorization through unchanged. |
| SSOGEN Gateway (all roles) | No. A Gateway identifies itself with a certificate in the TLS handshake, which a proxy that terminates TLS does not pass on. | Gateways must connect to SSOGEN’s own HTTPS port, not to a proxy that terminates TLS. |
7. Paths
Section titled “7. Paths”| Path | Used by | Rule |
|---|---|---|
/, /login, /apps/api, /apps/remote, /account/api, /logos/... |
People | Public. |
/oauth2/..., /oidc/..., /saml/..., /.well-known/... |
Applications and identity providers | Public. |
/admin |
Administrators | SSOGEN does not require it to be public. Limiting it by network at the proxy is your choice; run the six checks of section 9 afterwards. |
/agent/... |
Web Agents and Java agents | Do not block. Pass Authorization through unchanged. |
/edge/... |
SSOGEN Gateways | Do not block. See section 6. |
/scim/... |
An identity provider that provisions people | Only if you use SCIM. Limit it to that provider’s addresses if you can. |
/connector/... |
The connector service | Only if you use it. |
/setup |
The first administrator | Reachable until setup is done; after that it refuses everything. |
/health/live, /health/ready, /actuator/health |
The load balancer | For the balancer only. |
8. Health check and stopping a node
Section titled “8. Health check and stopping a node”| Probe | Answer |
|---|---|
/health/ready |
200 with an empty body when the node can serve; 503 when it cannot. |
/health/live |
200 while the process runs. |
/actuator/health |
200 (also when its status is DEGRADED); 503 for DOWN. |
Send SSOGEN’s own host name in the Host header of the probe, or probe /actuator/health. /health/ready and /health/live answer 421 for any other host, an IP address included, which would mark every node down.
When a node is stopped it finishes the requests in flight for up to 20 seconds. Set the balancer’s drain time to at least that.
9. After you set it up: six checks
Section titled “9. After you set it up: six checks”Replace <your-domain> with the name in SSOGEN’s address. Run them from a machine outside the proxy’s network.
-
Host preserved. This prints
200:curl -s -o /dev/null -w '%{http_code}\n' https://<your-domain>/health/readyThe same path under another name that points at the proxy must print
421, never200:curl -sk -o /dev/null -w '%{http_code}\n' --resolve other.example:443:<proxy-address> https://other.example/health/ready -
Agents and Gateways can reach their paths (if you use them). The answer must come from SSOGEN; a refusal for lack of credentials is the expected one. A timeout, or a 403 or 404 from the proxy itself, means the path is blocked.
curl -s -o /dev/null -w '%{http_code}\n' https://<your-domain>/agent/configuration -
HTTP goes to HTTPS. This prints a 301 or 302 that points at
https://<your-domain>/:curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' http://<your-domain>/ -
The client address is recorded. Sign in as a test person from a machine whose public address you know. In the Admin Console open Monitoring, Audit: the From column of that sign-in shows that machine’s address, not the proxy’s.
-
A header sent by the client changes nothing. From the same machine, sign in as the test person with a wrong password while your client adds these headers to the sign-in request:
X-Forwarded-For: 203.0.113.7,X-Real-IP: 203.0.113.7,CF-Connecting-IP: 203.0.113.7andForwarded: for=203.0.113.7. In Monitoring, Audit, the From of that refused sign-in must be your own address, never203.0.113.7. If it shows203.0.113.7, the proxy passes the client’s header on, or the hop count is higher than the number of proxies: correct that first. Repeat the check from a machine on your internal network, through the proxy: its From must be that machine’s own address. -
SSOGEN’s port is not reachable from outside. This must time out or be refused:
curl -k --connect-timeout 5 https://<ssogen-host>:9941/health/liveIf it answers, restrict the port to the proxy in the firewall.
Run checks 4, 5 and 6 again after any change to the proxy or to the three settings of section 5.