An HTTPS redirect loop in WordPress usually appears after TLS starts terminating at a load balancer, reverse proxy, or CDN edge. The browser already requests the secure URL, but /wp-admin/, /wp-login.php/, or the front page keeps redirecting back to the same HTTPS address until the client stops with a too-many-redirects error.
WordPress decides whether a request is secure through is_ssl(), and FORCE_SSL_ADMIN or normal canonical redirects use that result early in the request. If PHP never maps the trusted forwarded scheme to $_SERVER['HTTPS'], and the origin web server still runs a generic HTTP-to-HTTPS redirect, both WordPress and the origin can keep trying to upgrade a request that already arrived at the edge over HTTPS.
A stable fix needs three things to agree with each other: the edge must send one trusted HTTPS header, WordPress must map that header in /wp-config.php/ before core loads, and the origin redirect must skip requests whose forwarded scheme is already https. Only trust headers added by infrastructure under direct control, and note that Amazon CloudFront removes X-Forwarded-Proto before forwarding to a custom origin unless a different origin header or origin-side HTTPS strategy is used.
$ curl -skIL --max-redirs 5 https://www.example.com/wp-admin/ HTTP/2 302 location: https://www.example.com/wp-admin/ ##### snipped ##### curl: (47) Maximum (5) redirects followed
The loop covered here repeats the same https://... target. Redirects that switch between different hostnames or from http:// to https:// only once are a different problem.
location / {
proxy_pass http://wordpress-origin;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
Use the exact header name that the edge really sends. X-Forwarded-Proto is common, but some platforms use a fixed custom header or CloudFront-Forwarded-Proto instead.
Amazon CloudFront removes X-Forwarded-Proto before forwarding to a custom origin. Use CloudFront-Forwarded-Proto through an origin request policy or a fixed custom origin header when the behavior already forces HTTPS.
$ sudo vi /var/www/example.com/public_html/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.
Keep only one FORCE_SSL_ADMIN definition in the file, and never trust client-supplied forwarded headers on a directly reachable origin.
# Apache VirtualHost example
SetEnvIfNoCase X-Forwarded-Proto "^https$" forwarded_https=on
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteCond %{ENV:forwarded_https} !=on
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
Change the redirect rule that already handles the site instead of stacking a second HTTPS redirect somewhere else.
This verified pattern is best placed in the active VirtualHost or server config for the site rather than a second, disconnected rewrite fragment.
On Nginx origins, the same rule applies: the HTTPS redirect must key off the trusted forwarded-scheme header instead of the local request scheme alone.
# Ubuntu or Debian $ sudo apache2ctl -t Syntax OK $ sudo systemctl reload apache2 # Fedora, Red Hat, or CentOS Stream $ sudo httpd -t Syntax OK $ sudo systemctl reload httpd
If the redirect lives only in .htaccess, save the file and verify carefully; the important part is that the redirect skips requests whose trusted forwarded scheme is already https.
Related: How to test Apache configuration
Related: How to manage the Apache web server service
$ curl -skIL --max-redirs 5 https://www.example.com/wp-admin/ HTTP/2 302 location: https://www.example.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.example.com%2Fwp-admin%2F&reauth=1 HTTP/2 200 $ curl -skI https://www.example.com/ HTTP/2 200
If the loop disappears but WordPress still emits http:// URLs or the wrong hostname, fix the public site URL and cached content separately.