2026年11月12日

2026年11月12日

WordPress HTTP to HTTPS 301 Redirect: .htaccess, Nginx and Cloudflare

This article is a localized edition of the Japanese original. Read the Japanese original

Once an SSL certificate is installed, 301-redirect requests for http:// to https:// so the site has a single URL. A wrong setup leads to "too many redirects" (ERR_TOO_MANY_REDIRECTS) or a mix of www and non-www URLs. This article covers the order of work, Apache (.htaccess) and Nginx settings, how to avoid loops behind load balancers and Cloudflare, and how to verify the result.

In the examples, the canonical URL is https://example.com (without www). The rules for standardizing on www are shown as well.

Order of work

  1. Confirm HTTPS works first: make sure https://example.com/ opens in a browser without a certificate warning. If you also redirect the www URL, the certificate must include www.example.com too.
  2. Switch the WordPress URLs to https: in the admin, go to Settings → General and change "WordPress Address (URL)" and "Site Address (URL)" to https://example.com.
  3. Set up the redirect on the server: add the .htaccess or Nginx configuration shown below.
  4. Verify: use curl to confirm every entry point reaches the canonical URL with a single 301.

Back up your files and database before you start.

Check the current behavior

First, see how the site responds now. -I tells curl to show only the headers.

# Show only the status code and redirect target
curl -sI http://example.com/ | grep -iE '^(HTTP|location)'

# Follow redirects and show the final URL and redirect count (a loop stops at the limit)
curl -sIL --max-redirs 10 -o /dev/null -w '%{http_code} %{num_redirects} %{url_effective}\n' http://www.example.com/

WordPress settings

Change the URLs in Settings → General

Set "WordPress Address (URL)" and "Site Address (URL)" to URLs that start with https://. If the login screen appears after saving, log in again at the https URL. If WP-CLI is available, you can check and change them with these commands.

wp option get home
wp option get siteurl
wp option update home 'https://example.com'
wp option update siteurl 'https://example.com'

If wp-config.php defines WP_HOME or WP_SITEURL, those values take precedence and the admin fields cannot be edited. In that case, change the values in wp-config.php to https.

Once the site can be served over HTTPS, Tools → Site Health may show a button for switching the site to HTTPS.

Replace http URLs in your content

If older posts still contain image URLs like http://example.com/wp-content/uploads/..., browsers show mixed content warnings. When replacing them with WP-CLI, check the number of matches with --dry-run first.

wp search-replace 'http://example.com' 'https://example.com' --all-tables --dry-run
# Run it after checking the counts
wp search-replace 'http://example.com' 'https://example.com' --all-tables

Redirect with Apache (.htaccess)

Add the rules to the root .htaccess before the # BEGIN WordPress line. Anything inside the WordPress block is overwritten when, for example, permalink settings are saved.

Standardize on non-www

Both http requests and www requests go to https://example.com with a single 301.

# Redirect HTTP or www requests to https://example.com with a single 301
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTPS} off [OR]
RewriteCond %{HTTP_HOST} ^www\. [NC]
RewriteRule ^ https://example.com%{REQUEST_URI} [R=301,L]
</IfModule>

%{REQUEST_URI} holds the path, and mod_rewrite carries over the query string (such as ?x=1) automatically.

Standardize on www

# Redirect HTTP or non-www requests to https://www.example.com with a single 301
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTPS} off [OR]
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteRule ^ https://www.example.com%{REQUEST_URI} [R=301,L]
</IfModule>

Behind a load balancer or proxy

When a load balancer or CDN terminates HTTPS and forwards requests to the server over HTTP, every request looks like HTTP to the server, so %{HTTPS} off always matches and the redirect loops. In that setup, decide based on the X-Forwarded-Proto header added by the proxy.

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
</IfModule>

Requests without the header (such as load balancer health checks) are redirected too, so if a health check fails because of the 301, add a condition that excludes its path.

Redirect with Nginx

A server block for HTTP sends requests to HTTPS, and another block sends HTTPS requests for www to the canonical host.

# HTTP goes to https://example.com with or without www
server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;
    return 301 https://example.com$request_uri;
}

# https://www.example.com also goes to https://example.com
server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name www.example.com;
    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    return 301 https://example.com$request_uri;
}

# Main site
server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name example.com;
    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    # Your usual settings: root, location, PHP-FPM and so on
}

http2 on; is the syntax for Nginx 1.25.1 and later. On older Nginx, write listen 443 ssl http2; instead (newer versions print a deprecation warning for listen ... http2). To standardize on www, swap the server_name values of the second and third blocks and point the return targets to https://www.example.com.

After changing the configuration, test the syntax and then reload.

sudo nginx -t && sudo systemctl reload nginx

Prevent redirect loops

Tell WordPress about HTTPS behind a proxy

Behind a load balancer, Cloudflare and similar services, PHP sees an HTTP connection, so WordPress concludes it is being served over HTTP and may keep redirecting to https, for example in the admin. If the proxy sets X-Forwarded-Proto, add the following to wp-config.php above /* That's all, stop editing! */.

<?php
// Make WordPress treat requests the proxy received over HTTPS as HTTPS
if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && 'https' === $_SERVER['HTTP_X_FORWARDED_PROTO'] ) {
	$_SERVER['HTTPS'] = 'on';
}

// Restrict the admin and login to HTTPS
define( 'FORCE_SSL_ADMIN', true );

Anyone can send this header, so use this only when the server cannot be reached directly (requests arrive only through the proxy).

Cloudflare SSL/TLS setting

When Cloudflare's SSL/TLS encryption mode is "Flexible", Cloudflare connects to your server over HTTP. If the server also redirects to HTTPS, every HTTP request from Cloudflare is sent back to HTTPS and the redirect loops. Install a certificate on the server and set the mode to "Full (strict)" (Cloudflare's label as of October 2026).

If it still loops

Narrow it down in this order.

# 1. Are the WordPress URLs set to https?
wp option get home
wp option get siteurl

# 2. Does the loop stop with .htaccess temporarily moved aside?
mv .htaccess .htaccess.bak

# 3. Does the loop stop with all plugins deactivated? (if so, reactivate them one by one to find the cause)
wp plugin list --status=active --field=name > active-plugins.txt   # note the active ones first
wp plugin deactivate --all
wp plugin activate plugin-slug   # replace plugin-slug with a name saved in active-plugins.txt

When you're done, rename .htaccess.bak back and reactivate the plugins you turned off. If you use an SSL or redirect plugin, it can double up with the server-side redirect and cause a loop, so use only one of the two.

Verify the setup

With https://example.com as the canonical URL, the correct state is that all three of the following entry points reach the canonical URL with a single 301, and the canonical URL returns 200.

for u in http://example.com/ http://www.example.com/ https://www.example.com/; do
  curl -sI "$u" | grep -iE '^(HTTP|location)'
done
curl -sI https://example.com/ | grep -iE '^HTTP'

Expected results:

Requested URL Response Redirects to
http://example.com/ 301 https://example.com/
http://www.example.com/ 301 https://example.com/
https://www.example.com/ 301 https://example.com/
https://example.com/ 200 None

Browsers cache 301s, so while you experiment you can test with R=302 or return 302 and switch to 301 once everything works. When checking in a browser, a private window is less affected by the cache.

Summary

  • Confirm HTTPS works, switch both URLs in Settings → General to https, then add the server-side redirect
  • On Apache, add rules before # BEGIN WordPress that send http and www requests to the canonical URL with a single 301
  • On Nginx, use return 301 in a server block for HTTP and one for www
  • Behind a proxy or Cloudflare, decide by X-Forwarded-Proto, and set Cloudflare to "Full (strict)"
  • Use curl to confirm every entry point reaches the canonical URL with a single 301

Related articles

Questions about WordPress? The publisher of this site is based in Japan and replies in Japanese. Contact (Japanese)

お気軽にご相談ください

お見積りへ お問い合わせへ
▲