A reverse-proxied WordPress site can already be serving browsers over HTTPS while PHP still thinks every request arrived over plain HTTP. When that happens, WordPress can build insecure login redirects, wrong admin URLs, or mixed-scheme asset links even though the edge already handled TLS.
The decision point is is_ssl(), which depends on request variables such as $_SERVER['HTTPS'] very early in the bootstrap process. A load balancer, CDN, or reverse proxy usually passes the original viewer scheme through a trusted header such as X-Forwarded-Proto, and the practical fix is to map that header in /wp-config.php/ before the rest of WordPress loads. When the admin area must remain on HTTPS, FORCE_SSL_ADMIN should line up with the same trusted signal.
Only trust headers that are injected or rewritten by infrastructure under direct control. A directly reachable origin must not accept client-supplied forwarded-proto values, and platforms such as CloudFront can use a different header from the common X-Forwarded-Proto pattern. This page stays focused on the trust mapping itself; redirect loops, origin-side redirects, and public URL cleanup remain separate follow-up tasks.
Steps to make WordPress trust forwarded HTTPS behind a reverse proxy:
- Open a terminal session in the WordPress document root.
$ cd /var/www/example.com/public_html
Run the remaining relative commands from the directory that contains the live /wp-config.php/ file. Some managed hosting layouts keep that file one level above the public document root, so adjust the path when the active configuration lives elsewhere.
- Confirm which trusted header the reverse proxy, load balancer, or CDN sends to report the original viewer scheme.
location / { proxy_pass http://wordpress-origin; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-Proto $scheme; }X-Forwarded-Proto is the common pattern, but the inserted header name must match the edge that actually fronts WordPress.
Use only header names that are injected or sanitized by infrastructure under direct control. A directly reachable origin must not trust arbitrary client-supplied forwarded headers.
- Back up the current /wp-config.php/ file before changing HTTPS detection.
$ sudo cp wp-config.php wp-config.php.bak-$(date +%Y%m%d%H%M%S)
- Check whether the file already defines FORCE_SSL_ADMIN or a forwarded-proto mapping.
$ grep -nE "FORCE_SSL_ADMIN|HTTP_[A-Z_]*FORWARDED_PROTO|\\$_SERVER\\['HTTPS'\\]" wp-config.php
No output means none of these lines are currently set in that file.
Update existing definitions in place instead of adding a second FORCE_SSL_ADMIN or competing proxy check.
- Edit /wp-config.php/ and place the trusted-header mapping above the final /* That's all, stop editing! Happy publishing. */ line.
$ sudo vi wp-config.php
<?php define( 'FORCE_SSL_ADMIN', true ); if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && false !== strpos( $_SERVER['HTTP_X_FORWARDED_PROTO'], 'https' ) ) { $_SERVER['HTTPS'] = 'on'; }Replace HTTP_X_FORWARDED_PROTO with the actual trusted header name when the edge uses something different.
The strpos check covers comma-separated values such as http,https that can appear when multiple proxies append scheme data.
If the site intentionally allows plain-HTTP admin access on an isolated internal network, keep the forwarded-header mapping and adjust FORCE_SSL_ADMIN to match that policy instead of duplicating the constant elsewhere.
- Extend the condition only when the edge can send more than one trusted forwarded-proto header.
<?php if ( ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && false !== strpos( $_SERVER['HTTP_X_FORWARDED_PROTO'], 'https' ) ) || ( isset( $_SERVER['HTTP_CLOUDFRONT_FORWARDED_PROTO'] ) && false !== strpos( $_SERVER['HTTP_CLOUDFRONT_FORWARDED_PROTO'], 'https' ) ) ) { $_SERVER['HTTPS'] = 'on'; }Keep only the header names that the real edge emits. Do not leave unused alternatives in the live file.
- Probe the origin with and without the trusted header to confirm that WordPress now builds the secure admin redirect.
$ curl -sI http://127.0.0.1:18080/wp-admin/ | sed -n '1,8p' HTTP/1.1 302 Found X-Redirect-By: WordPress Location: http://127.0.0.1:18080/wp-login.php?redirect_to=http%3A%2F%2F127.0.0.1%3A18080%2Fwp-admin%2F&reauth=1 $ curl -sI -H 'X-Forwarded-Proto: https' http://127.0.0.1:18080/wp-admin/ | sed -n '1,8p' HTTP/1.1 302 Found X-Redirect-By: WordPress Location: https://127.0.0.1:18080/wp-login.php?redirect_to=https%3A%2F%2F127.0.0.1%3A18080%2Fwp-admin%2F&reauth=1
Replace 127.0.0.1:18080 with an origin hostname, container port, or internal upstream URL that reaches WordPress before the reverse proxy adds its own TLS handling. Add a Host header when virtual-host routing depends on the public site name.
- Load /wp-login.php/ or /wp-admin/ through the real reverse proxy and confirm the browser now stays on HTTPS for login, admin, and generated asset URLs.
If the browser still loops or falls back to plain-HTTP URLs, the public site URL, the trusted header name, or the origin-side redirect logic is still misaligned.
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.
