Your SSL certificate is installed, the padlock works on https://yoursite.nl, but typing yoursite.nl in the address bar still drops you on http://. Or you added a redirect rule, the site briefly worked, and now every URL throws ERR_TOO_MANY_REDIRECTS. Both situations come from the same root cause: forcing WordPress to HTTPS is a four-layer problem and most tutorials only cover one layer.
Why your WordPress site still loads over HTTP after installing SSL
Forcing the whole site to HTTPS means four things must agree, in order:
- A valid SSL certificate is installed and listening on port 443 at the web server level. If
https://yoursite.nldoes not work in a browser when you type it manually, no amount of WordPress configuration will fix anything. Stop here and fix the certificate first. - The web server returns a 301 redirect from HTTP to HTTPS for every URL. This is not WordPress's job. Apache does it via
.htaccessor a<VirtualHost>directive, nginx does it in aserverblock. WordPress has no control over this. - WordPress's own URL settings (
siteurlandhome) point athttps://so that links generated in menus, canonical tags, login redirects, andwp_redirect()calls all use the secure scheme. - The HTTP URLs already serialized in the database get rewritten to
https://. Old post content, widget settings, theme customizer values, and plugin options can all contain hardcodedhttp://yoursite.nl/...URLs that survive a settings change.
Skip layer 2 and the homepage works but search engines and bookmarks still hit HTTP. Skip layer 3 and you get mixed content warnings. Skip layer 4 and the padlock breaks the moment a visitor opens an old blog post.
The methods below handle layers 2 and 3. Layer 1 is your hosting panel's job, and layer 4 is covered in detail in the mixed content article. The trickiest part of getting layer 2 right is making sure your web server type matches the method you pick: an .htaccess file is silently ignored on nginx. There is no error in the log, no warning in the dashboard, no hint that something is wrong. The file simply does not exist as far as nginx is concerned. If you are on nginx and following an Apache tutorial, you can spend an entire afternoon editing .htaccess and wonder why nothing changes.
Find out which web server you have before picking a method. The quickest way is to check your hosting panel: most panels (cPanel, Plesk, DirectAdmin) show the web server type on the dashboard or under "Server Information." If your host does not expose it, open your browser's DevTools (F12 or Ctrl+Shift+I), go to the Network tab, reload https://yoursite.nl/, click the first request and look at the Response Headers section for a Server: header. You will see Apache/2.4.x, nginx/1.x, LiteSpeed, or cloudflare (if Cloudflare is in front, see the dedicated section below to find the origin server type).
If you have SSH access:
curl -sI https://yoursite.nl/ | grep -i server
LiteSpeed reads .htaccess files in Apache-compatible mode, so the Apache method works there too. Pure nginx does not.
Method 1: WP 5.7+ one-click HTTPS migration (the safest option)
If you are on WordPress 5.7 or newer and your site is straightforward (single domain, no WP_HOME or WP_SITEURL constants in wp-config.php), the dashboard already has a built-in tool that handles layers 3 and 4 in one click. WordPress 5.7 introduced wp_is_https_supported(), wp_update_urls_to_https(), and wp_replace_insecure_home_url() specifically to make HTTPS migration a button instead of a project.
Prerequisites.
- WordPress 5.7 or newer.
- A valid SSL certificate already installed and reachable on
https://yoursite.nl. The tool runs an actual HTTPS request against your own URL twice a day viawp_is_https_supported()and only enables the button when the request succeeds. - Site Address and WordPress Address use the same domain. Sites where they differ on domain are unsupported by the one-click tool.
- No
WP_HOMEorWP_SITEURLconstants defined inwp-config.php. This is the gotcha that traps most readers. The constants override the database values that the migration writes, so the tool either refuses to run or appears to succeed but reverts on the next page load.
Steps.
- Log in to the WordPress admin.
- Go to Tools > Site Health > Status.
- Look for the "Your website does not use HTTPS" critical issue. Since 5.7, absent HTTPS is rated critical severity, not just recommended.
- Click Update your site to use HTTPS. The button only appears when the underlying HTTPS check has passed. If you do not see the button, check the troubleshooting list below.
- WordPress runs
wp_update_urls_to_https(), which updates thesiteurlandhomeoptions and rewrites HTTP URLs found in the database.
Verification. Reload the front end in a fresh incognito window. The padlock should be solid. Visit a few old blog posts to confirm. Then check the database values: go to Settings > General in wp-admin and verify that both WordPress Address (URL) and Site Address (URL) start with https://.
WP-CLI alternative:
wp option get siteurl
wp option get home
Both should return https://yoursite.nl.
If the button does not appear. Check wp-config.php for these lines:
define( 'WP_HOME', '...' );
define( 'WP_SITEURL', '...' );
If they exist, the one-click tool is disabled because the constants would override anything it writes. You have two options: remove the constants and let the database hold the URLs (then the one-click tool works), or keep the constants and use Method 4 instead. There is no way to make the one-click tool work alongside hardcoded constants.
The one-click tool does not handle layer 2. It updates URLs and rewrites stored content, but it does not install a server-level redirect. After running it, you still need Method 2 (Apache) or Method 3 (nginx) to ensure that visitors typing http://yoursite.nl get a 301 to the HTTPS version.
Method 2: Apache .htaccess redirect
If your origin runs Apache (or LiteSpeed, which reads .htaccess in Apache-compatible mode), this is the standard layer-2 fix. It tells the server to issue a 301 redirect for any incoming HTTP request before WordPress is even loaded.
Prerequisites.
- Apache 2.4 or LiteSpeed.
mod_rewriteenabled. On most shared hosts it already is; if you are unsure, check your hosting panel's Apache module list or ask your host. If you have SSH access:apachectl -M | grep rewriteshould outputrewrite_module.AllowOverride All(or at minimumAllowOverride FileInfo) for the WordPress directory in the main Apache config. Shared hosts almost always set this. If your host does not, the rule below has no effect.- Write access to
.htaccessin the WordPress root.
Steps.
-
Open
.htaccessin the WordPress root directory over SFTP or your control panel's file manager. -
Find the existing WordPress block. It looks like this:
# BEGIN WordPressRewriteEngine On RewriteBase / RewriteRule ^index\.php$ - [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . /index.php [L] # END WordPress -
Add the HTTPS redirect rule above the WordPress block. Place it inside its own
<IfModule>so it does not interfere with WordPress's pretty-permalinks rules:RewriteEngine On RewriteCond %{HTTPS} off RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L] -
Save the file. Do not modify the WordPress block itself; that block is regenerated by Settings > Permalinks > Save Changes and any custom rules inside it get wiped.
What this rule does, line by line. RewriteEngine On activates mod_rewrite for this directory. RewriteCond %{HTTPS} off checks the server variable HTTPS, which Apache sets to on for SSL connections and leaves blank otherwise. RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L] captures any path, prepends https:// and the original host, and issues a permanent (301) redirect. The L flag tells mod_rewrite to stop processing more rules once this one matches.
The complete final .htaccess should look like this:
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
# BEGIN WordPress
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
# END WordPress
Verification. Open a fresh incognito window, open DevTools (F12), go to the Network tab, check Preserve log, and visit http://yoursite.nl/some-post/. The first request should show a 301 Moved Permanently status with a Location header pointing to https://yoursite.nl/some-post/. Repeat for the homepage and a few other URLs. If the second hop (the https:// URL) returns another redirect that points back to http://, you are about to enter a loop. Stop and read the Cloudflare and reverse-proxy sections below before continuing.
If you have SSH access:
curl -sI http://yoursite.nl/some-post/
Expected output: HTTP/1.1 301 Moved Permanently with Location: https://yoursite.nl/some-post/.
A note on .htaccess performance. Apache's official documentation recommends against .htaccess files when you have access to the main server config, because Apache must look for .htaccess at every directory level on every request and rewrite rules in .htaccess are recompiled per request rather than cached. On shared hosting you do not have a choice; on a VPS or dedicated server, put the same rules in a <Directory> block in your virtual host config instead and disable .htaccess entirely.
Method 3: nginx server block
This method requires SSH access to your server, or a managed host that lets you edit nginx configuration through their dashboard.
nginx does not read .htaccess files. It does not warn you about them, it does not log them, it simply does not parse them. If you are on nginx, the redirect must live in a server block in your main nginx config or one of its conf.d/ includes. There is no way around this.
Prerequisites.
- nginx 1.x.
- Root access to edit nginx config files (most managed hosts handle this themselves through their dashboard).
- The ability to reload nginx after editing.
Steps.
-
Locate your site's nginx config. On Debian and Ubuntu it usually lives at
/etc/nginx/sites-available/yoursite.nlwith a symlink insites-enabled/. On RHEL and CentOS it lives at/etc/nginx/conf.d/yoursite.nl.conf. -
You should have two
serverblocks: one listening on port 80 (HTTP), one on port 443 (HTTPS). If you only have a port-80 block, your SSL is not actually configured at the nginx layer and you need to fix that first. -
Replace the entire port-80 block's contents with a single
return 301directive. The whole point of this block becomes "redirect everything to HTTPS and stop":server { listen 80; listen [::]:80; server_name yoursite.nl www.yoursite.nl; return 301 https://yoursite.nl$request_uri; } -
Test the nginx config before reloading:
nginx -tExpected output.
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful -
Reload nginx:
systemctl reload nginx
Why return 301 and not rewrite. The nginx docs state that return is processed immediately without regex evaluation, making it materially faster than the equivalent rewrite ^ https://... directive. There is no reason to use rewrite for a blanket HTTP-to-HTTPS redirect.
Why the redirect goes to a hardcoded https://yoursite.nl and not to https://$host. If a visitor hits the IP directly, or sets a Host header for a domain that does not actually live on this server, $host will reflect that. Hardcoding the canonical hostname forces every redirect to land on the URL you actually want indexed, which also collapses any www vs non-www ambiguity in the same hop. If you want to keep www, use https://www.yoursite.nl$request_uri instead.
Verification. Same test as Method 2: open DevTools, visit http://yoursite.nl/some-post/ in an incognito window with Preserve log enabled, and confirm the first request is a 301 with a Location pointing to https://. Check the Server response header: it should say nginx. If it says Apache or cloudflare, you edited the wrong layer and the change has no effect.
If you have SSH access:
curl -sI http://yoursite.nl/some-post/
Expected output: HTTP/1.1 301 Moved Permanently with Location: https://yoursite.nl/some-post/ and Server: nginx/1.24.0.
Method 4: wp-config.php constants
Methods 2 and 3 handle layer 2 (the server-level redirect). Method 1 handles layers 3 and 4 (URL settings and database content). What remains is the layer-3 case where the one-click tool does not apply: managed hosts that hardcode WP_HOME and WP_SITEURL in wp-config.php, multisite installs, and any environment where the canonical URL needs to be defined in code rather than in the database.
When to use this method.
- You are on WordPress older than 5.7.
- Your host already defines
WP_HOME/WP_SITEURLinwp-config.phpand you cannot remove them. - You want the URL pinned in version control instead of in the database.
- The one-click tool refuses to run for any of the reasons in Method 1.
Steps.
-
Open
wp-config.phpin the WordPress root. -
Find the line that says
/* That's all, stop editing! Happy publishing. */. -
Above that line, add or update the constants:
define( 'WP_HOME', 'https://yoursite.nl' ); define( 'WP_SITEURL', 'https://yoursite.nl' ); -
No trailing slash. Match
wwwvs non-wwwto whatever your SSL certificate covers. -
Save the file.
What these constants actually do. The official wp-config.php documentation is explicit: WP_SITEURL "will not change the database stored value" and "the URL will revert to the old database value if this line is ever removed." The constants override the home and siteurl options in wp_options at runtime; they do not write to the database. That is exactly what you want when the dashboard is unreachable, when you are deploying via Git, or when you want the canonical URL pinned in code.
FORCE_SSL_ADMIN is not a substitute for any of this. The constant's name is misleading. force_ssl_admin() is documented as "Determines whether to force SSL used for the Administration Screens", and its scope is wp-admin and wp-login.php only. Setting FORCE_SSL_ADMIN to true does not force the public site to HTTPS, does not redirect HTTP visitors, and does not affect any URL outside the admin area. It is a security hardening setting for the back office, not a site-wide HTTPS switch. Many tutorials confuse the two. The WordPress login redirect loop article covers what happens when FORCE_SSL_ADMIN is enabled on a site that cannot actually serve the admin over HTTPS.
Verification. Reload the front end. Inspect the page source. The <link rel="canonical"> tag, the admin bar URLs, and any <base> tag should all start with https://. Then check wp-admin: it should load over HTTPS without any redirect chain.
After all four methods are in place where applicable, run a database search-replace to clean up any old http://yoursite.nl URLs still serialized in post content, postmeta, and options. The constants do not rewrite stored content; they only override the option values WordPress reads at runtime.
Cloudflare-specific configuration: Full (Strict), never Flexible
If Cloudflare sits in front of your WordPress site, your SSL/TLS mode setting can quietly create a redirect loop that looks like a WordPress problem but is not. Cloudflare's own documentation states that "Flexible" mode "creates HTTPS connections between your visitor and Cloudflare, but all connections between Cloudflare and your origin are made through HTTP". The mechanism that breaks WordPress is direct: Cloudflare talks to the origin over HTTP, the origin's .htaccess redirect (Method 2) or nginx server block (Method 3) sends back a 301 to HTTPS, Cloudflare receives the redirect and sends the visitor back to HTTPS, which hits Cloudflare, which again talks to the origin over HTTP. Infinite loop. The browser shows ERR_TOO_MANY_REDIRECTS after about 20 hops.
How to check your current Cloudflare SSL mode.
- Log in to Cloudflare and select the affected site.
- Open SSL/TLS > Overview.
- Look at the encryption mode. You will see one of: Off, Flexible, Full, Full (Strict).
What each mode means.
| Mode | Visitor to Cloudflare | Cloudflare to origin | Loop risk with Method 2 or 3 |
|---|---|---|---|
| Off | HTTP | HTTP | None (no HTTPS at all) |
| Flexible | HTTPS | HTTP | Yes |
| Full | HTTPS | HTTPS (cert not validated) | None |
| Full (Strict) | HTTPS | HTTPS (cert validated) | None |
The fix is to upgrade the mode, not to remove your origin redirect. Removing the origin redirect to "fix" the loop leaves you exposed: anyone who accesses the origin directly (by IP, by hostname, by an old DNS record) will get plain HTTP. Instead:
- Make sure the origin has a valid SSL certificate. Let's Encrypt is free and supported on every hosting panel; Cloudflare also offers free Origin CA certificates that are trusted by Cloudflare's edge but not by browsers, which is exactly what you want for an origin behind Cloudflare.
- Switch the SSL/TLS mode to Full (Strict). This validates the origin certificate against a trusted CA and refuses to talk to an origin with a missing or expired cert.
- Purge the Cloudflare cache: Caching > Configuration > Purge Everything. Wait around 30 seconds for the change to propagate.
- Reload the site in a fresh incognito window. The loop should be gone.
Cloudflare also explicitly recommends Full or Full (Strict) for "sites handling sensitive information (personalized data, user login)". Any WordPress site has user login at wp-admin, even if the front end is purely informational. Flexible mode is never the right answer for WordPress.
If you genuinely cannot install a certificate on the origin, the only workaround that does not create a loop is to remove the origin's HTTP-to-HTTPS redirect entirely and use Cloudflare's Always Use HTTPS page rule to do the redirect at the edge instead. This is a workaround, not a recommended posture: it leaves the origin reachable over plain HTTP for anyone who finds the IP, and it pushes responsibility for HTTPS enforcement entirely to Cloudflare. Use it as a stopgap while you set up a proper origin certificate.
For the symptom-side view of Cloudflare Flexible loops (and four other causes of ERR_TOO_MANY_REDIRECTS), see the too many redirects article.
Debugging the infinite redirect loop
If you have applied a method above and the site now throws ERR_TOO_MANY_REDIRECTS, the cause is almost always one of three things: the Cloudflare loop above, two layers both forcing HTTPS in disagreement, or a reverse proxy where WordPress and the proxy disagree about the current scheme. Diagnose before changing more.
Step 1: read the redirect chain. Open the site in an incognito window with DevTools on the Network tab, check Preserve log and Disable cache, and load the URL. You will see a sequence of 301/302 responses. Read the Location header on each.
http://yoursite.nltohttps://yoursite.nltohttp://yoursite.nlmeans two layers disagree about the canonical scheme. One is forcing HTTPS, the other is forcing HTTP. The Cloudflare Flexible scenario above produces exactly this pattern.- The chain keeps adding or stripping
wwwmeans your canonical hostname is inconsistent. Your.htaccessor nginx redirect points to the wrong host, orWP_HOMEandWP_SITEURLuse a different host than the redirect. - The loop only starts on
/wp-admin/meansFORCE_SSL_ADMINis set on a site that cannot actually serve the admin over HTTPS, usually because of a reverse-proxy or Cloudflare Flexible issue.
Step 2: confirm what the origin is actually doing. Bypass any CDN or proxy and hit the origin directly. From a server with shell access, or your own machine with the origin IP in /etc/hosts:
curl -sI --resolve yoursite.nl:443:ORIGIN_IP https://yoursite.nl/
curl -sI --resolve yoursite.nl:80:ORIGIN_IP http://yoursite.nl/
Replace ORIGIN_IP with your actual origin IP (Cloudflare hides it; check your hosting panel for the real address). The first command should return a 200 OK from the HTTPS endpoint. The second should return a single 301 pointing to https://yoursite.nl/. If the origin's HTTPS endpoint returns its own 301 redirect, your origin is in a self-referential loop and the fix is on the origin, not at the edge.
Step 3: check WordPress's URL settings. In wp-admin, go to Settings > General and verify that both WordPress Address (URL) and Site Address (URL) start with https://. If they show http://, run Method 1 or Method 4 to fix them.
WP-CLI alternative:
wp option get siteurl
wp option get home
Both should return https://yoursite.nl (or whatever canonical URL your certificate covers).
Reverse proxy and load balancer caveats: trust X-Forwarded-Proto only when safe
If WordPress runs behind a reverse proxy or load balancer that terminates TLS and then forwards plain HTTP to the origin, the PHP process sees $_SERVER['HTTPS'] empty and thinks the request was HTTP. Every link WordPress generates uses http://, every wp_redirect() call points to http://, and is_ssl() returns false. Combine that with FORCE_SSL_ADMIN and you get an instant redirect loop in wp-admin.
The fix is to tell WordPress to read the original scheme from the X-Forwarded-Proto header that the proxy sets. The WordPress HTTPS admin guide documents this exact pattern:
define( 'FORCE_SSL_ADMIN', true );
if (
isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] )
&& false !== strpos( $_SERVER['HTTP_X_FORWARDED_PROTO'], 'https' )
) {
$_SERVER['HTTPS'] = 'on';
}
Place this in wp-config.php above the /* That's all, stop editing! */ line. The check uses strpos rather than equality because the header can contain a comma-separated list (http,https) when multiple proxies have stamped it.
Critical security caveat: only do this when the origin is not directly reachable from the public internet. If the origin server accepts HTTP requests directly (not just from the proxy), a malicious client can set X-Forwarded-Proto: https in their request, and WordPress will trust it. This lets an attacker bypass HTTPS enforcement in scenarios where you assumed it was on, and in cookie-based authentication setups it can enable session hijacking. The X-Forwarded-Proto shim is only safe when one of these is true:
- The origin is bound to an internal IP that the public internet cannot reach.
- A firewall blocks all inbound traffic to the origin except from the proxy's known IP ranges.
- The proxy is the only listener on the origin's port, and the origin has no public DNS record of its own.
If your origin is reachable directly over HTTP, do not add this shim. Fix the network topology first: put the origin behind a private network or a firewall, then add the shim. Adding the shim on a publicly reachable origin creates a header-spoofing vulnerability that is worse than the redirect loop you were trying to fix.
AWS-specific: CloudFront uses a different header name. AWS CloudFront does not pass X-Forwarded-Proto in the same way; it sets Cloudfront-Forwarded-Proto. The PHP variable name becomes $_SERVER['HTTP_CLOUDFRONT_FORWARDED_PROTO']. Use only the header your actual infrastructure emits. AWS Application Load Balancer (ALB) and Elastic Load Balancer (ELB) do use the standard X-Forwarded-Proto header.
Verification. With the shim in place, reload wp-admin in a fresh incognito window. The login form should appear in a single request. Inspect the page source: <link rel="canonical"> should start with https://. Then check the is_ssl() return value from the admin:
wp eval 'echo is_ssl() ? "true" : "false";'
Expected output.
true
If it still returns false, the shim is not being executed before WordPress reads the scheme. Make sure the shim sits above the line require_once ABSPATH . 'wp-settings.php'; in wp-config.php, not below it.
When to escalate
If you have applied the right method for your stack and the site still loads over HTTP, or if the loop persists after working through the Cloudflare and reverse-proxy sections, collect this before contacting your host or a WordPress engineer:
- The exact symptom: which URL fails, in which browser, with which error string.
- The output of
curl -sI http://yoursite.nl/andcurl -sI https://yoursite.nl/from a separate machine. - The
Server:header from those requests, so you know which web server is actually answering. - The redirect chain from DevTools (every
Locationheader in order, copied verbatim). - Your Cloudflare SSL/TLS mode if Cloudflare is in the path.
- The contents of
.htaccess(Apache) or your nginx server block. - The
WP_HOME,WP_SITEURL, andFORCE_SSL_ADMINdefinitions fromwp-config.php, with database credentials redacted. - The output of
wp option get siteurlandwp option get home. - Whether your WordPress version is 5.7 or newer and whether the Site Health one-click HTTPS button appeared.
A site that fails to force HTTPS almost always has one of the patterns above as the root cause. The escalation list turns "it does not work" into a diagnostic conversation that resolves in minutes instead of hours.
How to keep HTTPS working after a migration
Migrations are where HTTPS configurations break. Domain changes, host moves, and certificate renewals all touch one of the four layers and rarely all of them in the same step. A short pre-migration checklist saves a lot of debugging:
- Write down the four values that matter before you start: your
WP_HOMEandWP_SITEURLsetting (constant or database), your Cloudflare SSL mode if applicable, your.htaccessor nginx redirect, and whetherFORCE_SSL_ADMINis set. - Run the database search-replace for the new domain on the new host, using WP-CLI or Better Search Replace as documented in the mixed content article. Skip the
guidcolumn. - Test the redirect chain with
curl -sIbefore pointing DNS at the new host. The new server should return a single 301 from HTTP to HTTPS, then a 200 on HTTPS. - Use Full (Strict) mode on Cloudflare from day one. Do not start in Flexible "for testing" and forget to upgrade.
- Pick one place to enforce HTTPS and disable it everywhere else. If your
.htaccessor nginx server block is doing the redirect, do not also enable a "Force HTTPS" toggle in an SEO or SSL plugin. Two layers of enforcement is the most common cause of redirect loops after a migration.