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.
$ 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.
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.
$ sudo cp wp-config.php wp-config.php.bak-$(date +%Y%m%d%H%M%S)
$ 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.
$ 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.
<?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.
$ 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.
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.