Top Tags

WordPress config

WordPress URL configuration, wp-config.php constants, and site URL management for deployments and migrations

Overview

This guide covers WordPress URL configuration—a critical aspect of WordPress administration that affects site accessibility, SEO, asset loading, and deployment workflows. Understanding these concepts is essential for system administrators, DevOps engineers, and developers working with WordPress in production environments.

URL Resolution Order

WordPress resolves site URLs using the following precedence (highest to lowest):

  1. Constants in wp-config.php (WP_HOME, WP_SITEURL)
  2. Filtered values via home_url and site_url filters
  3. Database options (home, siteurl in wp_options table)
  4. Installation defaults (set during WordPress setup)

Understanding this hierarchy is crucial for debugging URL-related issues and implementing proper multi-environment configurations.


Define URL

WordPress uses two core constants to manage site URLs: WP_HOME and WP_SITEURL. These constants override the values stored in the database and are essential for deployment configurations, migrations, and containerized environments.

Technical Details:

  • WP_HOME: The URL visitors use to access your site (front-end)
  • WP_SITEURL: The URL where WordPress core files are located
  • Both constants prevent URL changes via wp-admin (Settings → General)
  • Defined in wp-config.php or as environment variables
  • Take precedence over home and siteurl options in wp_options table

Internal WordPress Functions:

FunctionUsesReturns
home_url()WP_HOMEFront-end URL for visitors
site_url()WP_SITEURLWordPress installation URL
admin_url()WP_SITEURLAdmin area URL (/wp-admin/)
content_url()WP_SITEURLContent directory URL (/wp-content/)
includes_url()WP_SITEURLIncludes directory URL (/wp-includes/)
plugins_url()WP_SITEURLPlugins directory URL

URL Anatomy Example:

https://example.com/blog/wp-admin/edit.php ├─────────────────┘ └───────────────────┘ WP_HOME Path after WP_SITEURL WP_HOME: https://example.com WP_SITEURL: https://example.com/blog
php
1WORDPRESS_CONFIG_EXTRA:
2 define('WP_HOME','http://localhost');
3 define('WP_SITEURL','http://localhost');

Configuration Methods

1. wp-config.php (Static)

Add directly to wp-config.php before the "That's all, stop editing!" comment:

php
1define('WP_HOME', 'https://example.com');
2define('WP_SITEURL', 'https://example.com');

File Location Reference:

plaintext
1WordPress Root/
2├── wp-config.php ← Define constants here
3├── wp-settings.php ← Loaded after wp-config.php
4├── wp-content/
5├── wp-admin/
6└── wp-includes/

Placement in wp-config.php:

php
1<?php
2// Database settings
3define('DB_NAME', 'wordpress');
4define('DB_USER', 'root');
5define('DB_PASSWORD', 'password');
6define('DB_HOST', 'localhost');
7
8// URL Constants - ADD HERE (before authentication keys)
9define('WP_HOME', 'https://example.com');
10define('WP_SITEURL', 'https://example.com');
11
12// Authentication Unique Keys
13define('AUTH_KEY', '...');
14// ...
15
16/* That's all, stop editing! Happy publishing. */
17require_once ABSPATH . 'wp-settings.php';

2. Environment Variables (Docker/12-Factor)

For containerized deployments, use environment variables with fallback:

php
1define('WP_HOME', getenv('WP_HOME') ?: 'http://localhost');
2define('WP_SITEURL', getenv('WP_SITEURL') ?: 'http://localhost');

Docker Compose example:

yaml
1environment:
2 WP_HOME: https://production.com
3 WP_SITEURL: https://production.com
4 WORDPRESS_CONFIG_EXTRA: |
5 define('WP_HOME', getenv('WP_HOME'));
6 define('WP_SITEURL', getenv('WP_SITEURL'));

Complete Docker Compose Stack:

yaml
1version: '3.8'
2
3services:
4 wordpress:
5 image: wordpress:6.4-php8.2-apache
6 ports:
7 - "8080:80"
8 environment:
9 WORDPRESS_DB_HOST: db
10 WORDPRESS_DB_USER: wp_user
11 WORDPRESS_DB_PASSWORD: ${DB_PASSWORD}
12 WORDPRESS_DB_NAME: wordpress
13 WP_HOME: ${SITE_URL:-http://localhost:8080}
14 WP_SITEURL: ${SITE_URL:-http://localhost:8080}
15 WORDPRESS_CONFIG_EXTRA: |
16 define('WP_HOME', getenv('WP_HOME'));
17 define('WP_SITEURL', getenv('WP_SITEURL'));
18 define('WP_DEBUG', getenv('WP_DEBUG') === 'true');
19 define('WP_DEBUG_LOG', '/var/log/wordpress/debug.log');
20 define('FORCE_SSL_ADMIN', getenv('FORCE_SSL') === 'true');
21 volumes:
22 - wordpress_data:/var/www/html
23 - ./wp-content:/var/www/html/wp-content
24 depends_on:
25 - db
26
27 db:
28 image: mariadb:10.11
29 environment:
30 MYSQL_DATABASE: wordpress
31 MYSQL_USER: wp_user
32 MYSQL_PASSWORD: ${DB_PASSWORD}
33 MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
34 volumes:
35 - db_data:/var/lib/mysql
36
37volumes:
38 wordpress_data:
39 db_data:

Environment File (.env):

bash
1# .env file for Docker Compose
2SITE_URL=https://example.com
3DB_PASSWORD=secure_password_here
4DB_ROOT_PASSWORD=root_secure_password
5WP_DEBUG=false
6FORCE_SSL=true

Kubernetes ConfigMap Example:

yaml
1apiVersion: v1
2kind: ConfigMap
3metadata:
4 name: wordpress-config
5data:
6 WP_HOME: "https://example.com"
7 WP_SITEURL: "https://example.com"
8 WORDPRESS_CONFIG_EXTRA: |
9 define('WP_HOME', getenv('WP_HOME'));
10 define('WP_SITEURL', getenv('WP_SITEURL'));
11 define('FORCE_SSL_ADMIN', true);
12 define('DISALLOW_FILE_EDIT', true);

3. Subdirectory Installation

When WordPress core is in a subdirectory:

php
1define('WP_HOME', 'https://example.com');
2define('WP_SITEURL', 'https://example.com/wordpress');

Directory Structure for Subdirectory Installation:

plaintext
1public_html/ ← Document root (WP_HOME)
2├── index.php ← Modified loader
3├── .htaccess ← Rewrite rules
4└── wordpress/ ← WordPress core (WP_SITEURL)
5 ├── wp-admin/
6 ├── wp-content/
7 ├── wp-includes/
8 ├── wp-config.php
9 └── index.php

Modified Root index.php:

php
1<?php
2/**
3 * Front to the WordPress application.
4 * Modified to load WordPress from subdirectory.
5 */
6define('WP_USE_THEMES', true);
7require(__DIR__ . '/wordpress/wp-blog-header.php');

Root .htaccess Configuration:

apache
1# BEGIN WordPress Subdirectory
2<IfModule mod_rewrite.c>
3RewriteEngine On
4RewriteBase /
5RewriteRule ^index\.php$ - [L]
6
7# Don't rewrite requests to actual files or directories
8RewriteCond %{REQUEST_FILENAME} !-f
9RewriteCond %{REQUEST_FILENAME} !-d
10
11# Rewrite everything else to index.php
12RewriteRule . /index.php [L]
13</IfModule>
14# END WordPress Subdirectory

Use Cases for Subdirectory Installation:

ScenarioWP_HOMEWP_SITEURL
Clean URLs (core hidden)https://example.comhttps://example.com/wp
Multiple apps on domainhttps://example.com/bloghttps://example.com/blog/wordpress
Development isolationhttp://localhost:8080http://localhost:8080/wordpress

Database Direct Update (Migration/Emergency)

When wp-config.php is inaccessible, update URLs via SQL:

sql
1-- Update site URL
2UPDATE wp_options
3SET option_value = 'https://newdomain.com'
4WHERE option_name IN ('home', 'siteurl');
5
6-- Verify changes
7SELECT option_name, option_value
8FROM wp_options
9WHERE option_name IN ('home', 'siteurl');

Warning: This method doesn't update serialized data in post content or postmeta. Use WP-CLI or Search Replace DB for full migrations.

Database Schema Context:

The wp_options table stores WordPress configuration as key-value pairs:

sql
1-- wp_options table structure
2+-------------+---------------------+------+-----+---------+----------------+
3| Field | Type | Null | Key | Default | Extra |
4+-------------+---------------------+------+-----+---------+----------------+
5| option_id | bigint(20) unsigned | NO | PRI | NULL | auto_increment |
6| option_name | varchar(191) | NO | UNI | | |
7| option_value| longtext | NO | | | |
8| autoload | varchar(20) | NO | | yes | |
9+-------------+---------------------+------+-----+---------+----------------+

Complete Migration Query Set:

sql
1-- Start transaction for safety
2START TRANSACTION;
3
4-- Update core URL options
5UPDATE wp_options
6SET option_value = REPLACE(option_value, 'http://olddomain.com', 'https://newdomain.com')
7WHERE option_name IN ('home', 'siteurl');
8
9-- Update GUID in posts (permanent URLs)
10UPDATE wp_posts
11SET guid = REPLACE(guid, 'http://olddomain.com', 'https://newdomain.com');
12
13-- Update post content (images, links)
14UPDATE wp_posts
15SET post_content = REPLACE(post_content, 'http://olddomain.com', 'https://newdomain.com');
16
17-- Update post excerpts
18UPDATE wp_posts
19SET post_excerpt = REPLACE(post_excerpt, 'http://olddomain.com', 'https://newdomain.com');
20
21-- Verify changes before committing
22SELECT option_name, option_value FROM wp_options WHERE option_name IN ('home', 'siteurl');
23
24-- Commit if everything looks correct
25COMMIT;
26
27-- Or rollback if issues found
28-- ROLLBACK;

Serialized Data Warning:

WordPress stores complex data as PHP serialized strings. Direct SQL replacement breaks serialization:

php
1// Example serialized data in wp_options
2a:2:{s:4:"home";s:24:"http://olddomain.com";s:7:"version";s:5:"6.4.2";}
3// ↑ ↑
4// String length (24) Actual string
5
6// After naive replace (BROKEN):
7a:2:{s:4:"home";s:25:"https://newdomain.com";s:7:"version";s:5:"6.4.2";}
8// ↑ ↑
9// Still 24! Now 25 chars - LENGTH MISMATCH

This is why WP-CLI search-replace is strongly recommended for production migrations.

WP-CLI Method

Recommended for migrations and server-side automation:

bash
1# Update URLs
2wp option update home 'https://newdomain.com'
3wp option update siteurl 'https://newdomain.com'
4
5# Search-replace across all tables (handles serialized data)
6wp search-replace 'http://olddomain.com' 'https://newdomain.com' \
7 --all-tables --report-changed-only
8
9# Flush cache and rewrite rules
10wp cache flush
11wp rewrite flush

WP-CLI Search-Replace Options:

FlagDescription
--dry-runPreview changes without applying
--all-tablesInclude non-WordPress tables
--networkFor multisite, run on all sites
--skip-columns=<cols>Skip specific columns (e.g., guid)
--preciseUse PHP serialization for accuracy
--report-changed-onlyOnly report tables with changes
--log=<file>Log changes to file

Comprehensive Migration Script:

bash
1#!/bin/bash
2# WordPress URL Migration Script
3# Usage: ./migrate-urls.sh OLD_URL NEW_URL
4
5OLD_URL="${1:-http://staging.example.com}"
6NEW_URL="${2:-https://production.example.com}"
7BACKUP_FILE="pre-migration-$(date +%Y%m%d-%H%M%S).sql"
8
9echo "=== WordPress URL Migration ==="
10echo "From: $OLD_URL"
11echo "To: $NEW_URL"
12
13# Create backup
14echo "[1/5] Creating database backup..."
15wp db export "$BACKUP_FILE" --add-drop-table
16
17# Dry run first
18echo "[2/5] Analyzing changes (dry run)..."
19wp search-replace "$OLD_URL" "$NEW_URL" \
20 --all-tables \
21 --dry-run \
22 --report-changed-only
23
24# Confirm before proceeding
25read -p "Proceed with migration? (y/N): " confirm
26if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then
27 echo "Migration cancelled."
28 exit 0
29fi
30
31# Execute replacement
32echo "[3/5] Executing URL replacement..."
33wp search-replace "$OLD_URL" "$NEW_URL" \
34 --all-tables \
35 --precise \
36 --report-changed-only
37
38# Handle protocol changes (http → https)
39if [[ "$NEW_URL" == https://* && "$OLD_URL" == http://* ]]; then
40 echo "[3.5/5] Fixing mixed protocol references..."
41 OLD_HTTP="${OLD_URL/https:/http:}"
42 wp search-replace "$OLD_HTTP" "$NEW_URL" \
43 --all-tables \
44 --precise \
45 --report-changed-only
46fi
47
48# Flush caches
49echo "[4/5] Flushing caches..."
50wp cache flush
51wp transient delete --all
52wp rewrite flush
53
54# Verify
55echo "[5/5] Verifying configuration..."
56echo "Home URL: $(wp option get home)"
57echo "Site URL: $(wp option get siteurl)"
58
59echo ""
60echo "Migration complete! Backup saved: $BACKUP_FILE"

Multisite Migration:

bash
1# List all sites in network
2wp site list --fields=blog_id,url
3
4# Migrate specific site
5wp search-replace 'http://old.com' 'https://new.com' \
6 --url=http://old.com \
7 --all-tables-with-prefix
8
9# Migrate entire network
10wp search-replace 'http://old.com' 'https://new.com' \
11 --network \
12 --all-tables

Common Scenarios

Multisite Network

Each site in a network requires individual URL configuration:

php
1// Network-wide constants
2define('DOMAIN_CURRENT_SITE', 'example.com');
3define('PATH_CURRENT_SITE', '/');
4
5// Per-site URLs managed via wp_blogs table

Multisite Database Tables:

WordPress Multisite manages URLs across multiple tables:

sql
1-- Main site configuration
2SELECT * FROM wp_options WHERE option_name IN ('home', 'siteurl');
3
4-- Network sites registry
5SELECT blog_id, domain, path FROM wp_blogs;
6
7-- Per-site options (replace N with blog_id)
8SELECT * FROM wp_N_options WHERE option_name IN ('home', 'siteurl');
9
10-- Site mappings for domain mapping
11SELECT * FROM wp_domain_mapping;

Network Administration Constants:

php
1// Multisite network configuration
2define('WP_ALLOW_MULTISITE', true);
3define('MULTISITE', true);
4define('SUBDOMAIN_INSTALL', false); // false = subdirectory, true = subdomain
5define('DOMAIN_CURRENT_SITE', 'example.com');
6define('PATH_CURRENT_SITE', '/');
7define('SITE_ID_CURRENT_SITE', 1);
8define('BLOG_ID_CURRENT_SITE', 1);
9
10// Cookie domains for cross-site authentication
11define('COOKIE_DOMAIN', '.example.com');
12define('ADMIN_COOKIE_PATH', '/');
13define('COOKIEPATH', '/');
14define('SITECOOKIEPATH', '/');

HTTPS/SSL Migration

When moving from HTTP to HTTPS:

php
1// Force HTTPS
2define('FORCE_SSL_ADMIN', true);
3
4// Update URLs to HTTPS
5define('WP_HOME', 'https://example.com');
6define('WP_SITEURL', 'https://example.com');

Then update content URLs:

bash
1wp search-replace 'http://example.com' 'https://example.com' --all-tables

SSL-Related Constants Reference:

ConstantPurposeDefault
FORCE_SSL_ADMINForce HTTPS for wp-admin and wp-loginfalse
FORCE_SSL_LOGINForce HTTPS for login only (deprecated)false

Complete HTTPS Migration Checklist:

php
1// wp-config.php HTTPS configuration
2define('FORCE_SSL_ADMIN', true);
3define('WP_HOME', 'https://example.com');
4define('WP_SITEURL', 'https://example.com');
5
6// Force HTTPS for all content
7if (strpos($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '', 'https') !== false) {
8 $_SERVER['HTTPS'] = 'on';
9}

.htaccess HTTPS Redirect:

apache
1# Force HTTPS redirect
2<IfModule mod_rewrite.c>
3RewriteEngine On
4RewriteCond %{HTTPS} off
5RewriteCond %{HTTP:X-Forwarded-Proto} !https
6RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
7</IfModule>
8
9# HSTS Header (Strict Transport Security)
10<IfModule mod_headers.c>
11Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
12</IfModule>

nginx HTTPS Configuration:

nginx
1server {
2 listen 80;
3 server_name example.com www.example.com;
4 return 301 https://$server_name$request_uri;
5}
6
7server {
8 listen 443 ssl http2;
9 server_name example.com www.example.com;
10
11 ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
12 ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
13
14 # Modern SSL configuration
15 ssl_protocols TLSv1.2 TLSv1.3;
16 ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
17 ssl_prefer_server_ciphers off;
18
19 # HSTS
20 add_header Strict-Transport-Security "max-age=63072000" always;
21
22 root /var/www/html;
23 index index.php;
24
25 location / {
26 try_files $uri $uri/ /index.php?$args;
27 }
28
29 location ~ \.php$ {
30 fastcgi_pass php-fpm:9000;
31 fastcgi_param HTTPS on;
32 include fastcgi_params;
33 }
34}

Reverse Proxy / Load Balancer

For sites behind proxies (Cloudflare, nginx):

php
1// Trust proxy headers
2if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
3 $_SERVER['HTTPS'] = 'on';
4}
5
6define('WP_HOME', 'https://example.com');
7define('WP_SITEURL', 'https://example.com');

Complete Reverse Proxy Configuration:

WordPress needs to correctly detect the original client request when behind a proxy. Add this to wp-config.php:

php
1/**
2 * Reverse Proxy / Load Balancer Configuration
3 * Place before wp-settings.php require
4 */
5
6// Trust X-Forwarded-* headers from known proxies
7$trusted_proxies = [
8 '10.0.0.0/8', // Internal network
9 '172.16.0.0/12', // Docker default
10 '192.168.0.0/16', // Local network
11 '127.0.0.1', // Localhost
12];
13
14// Check if request is from trusted proxy
15$client_ip = $_SERVER['REMOTE_ADDR'] ?? '';
16$is_trusted = false;
17
18foreach ($trusted_proxies as $proxy) {
19 if (strpos($proxy, '/') !== false) {
20 // CIDR notation check (simplified)
21 list($subnet, $mask) = explode('/', $proxy);
22 if ((ip2long($client_ip) & ~((1 << (32 - $mask)) - 1)) == ip2long($subnet)) {
23 $is_trusted = true;
24 break;
25 }
26 } elseif ($client_ip === $proxy) {
27 $is_trusted = true;
28 break;
29 }
30}
31
32if ($is_trusted) {
33 // Set HTTPS from forwarded protocol
34 if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
35 $_SERVER['HTTPS'] = 'on';
36 }
37
38 // Set real client IP
39 if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
40 $forwarded_ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
41 $_SERVER['REMOTE_ADDR'] = trim($forwarded_ips[0]);
42 } elseif (isset($_SERVER['HTTP_X_REAL_IP'])) {
43 $_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_REAL_IP'];
44 }
45
46 // Set original host
47 if (isset($_SERVER['HTTP_X_FORWARDED_HOST'])) {
48 $_SERVER['HTTP_HOST'] = $_SERVER['HTTP_X_FORWARDED_HOST'];
49 }
50}
51
52define('WP_HOME', 'https://example.com');
53define('WP_SITEURL', 'https://example.com');

Cloudflare-Specific Configuration:

php
1// Cloudflare IP ranges (update periodically from cloudflare.com/ips)
2// Simplified version - use actual Cloudflare IPs in production
3if (isset($_SERVER['HTTP_CF_CONNECTING_IP'])) {
4 $_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_CF_CONNECTING_IP'];
5}
6
7if (isset($_SERVER['HTTP_CF_VISITOR'])) {
8 $visitor = json_decode($_SERVER['HTTP_CF_VISITOR']);
9 if ($visitor && $visitor->scheme === 'https') {
10 $_SERVER['HTTPS'] = 'on';
11 }
12}

nginx Proxy Headers Configuration:

nginx
1# nginx reverse proxy configuration
2location / {
3 proxy_pass http://wordpress-backend;
4
5 # Forward original request info
6 proxy_set_header Host $host;
7 proxy_set_header X-Real-IP $remote_addr;
8 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
9 proxy_set_header X-Forwarded-Proto $scheme;
10 proxy_set_header X-Forwarded-Host $host;
11 proxy_set_header X-Forwarded-Port $server_port;
12
13 # WebSocket support (if needed)
14 proxy_http_version 1.1;
15 proxy_set_header Upgrade $http_upgrade;
16 proxy_set_header Connection "upgrade";
17
18 # Timeouts for long-running requests
19 proxy_connect_timeout 60s;
20 proxy_send_timeout 60s;
21 proxy_read_timeout 60s;
22}

Traefik Labels for Docker:

yaml
1services:
2 wordpress:
3 image: wordpress:latest
4 labels:
5 - "traefik.enable=true"
6 - "traefik.http.routers.wordpress.rule=Host(`example.com`)"
7 - "traefik.http.routers.wordpress.entrypoints=websecure"
8 - "traefik.http.routers.wordpress.tls=true"
9 - "traefik.http.routers.wordpress.tls.certresolver=letsencrypt"
10 - "traefik.http.services.wordpress.loadbalancer.server.port=80"
11 environment:
12 WORDPRESS_CONFIG_EXTRA: |
13 if (isset($$_SERVER['HTTP_X_FORWARDED_PROTO']) && $$_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
14 $$_SERVER['HTTPS'] = 'on';
15 }
16 define('WP_HOME', 'https://example.com');
17 define('WP_SITEURL', 'https://example.com');

Troubleshooting

Redirect Loop

Mismatch between WP_HOME and actual URL. Check:

  • .htaccess rewrite rules
  • Proxy/CDN configuration
  • $_SERVER['HTTPS'] value

Debugging Redirect Loops:

php
1// Add to wp-config.php temporarily for debugging
2error_log('HTTPS: ' . ($_SERVER['HTTPS'] ?? 'not set'));
3error_log('REQUEST_URI: ' . $_SERVER['REQUEST_URI']);
4error_log('HTTP_HOST: ' . $_SERVER['HTTP_HOST']);
5error_log('SERVER_PORT: ' . $_SERVER['SERVER_PORT']);
6error_log('X-Forwarded-Proto: ' . ($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? 'not set'));

Common Redirect Loop Causes:

SymptomCauseSolution
Loop on all pagesWP_HOME/WP_SITEURL mismatch with actual URLVerify constants match access URL
Loop on HTTPS only$_SERVER['HTTPS'] not setAdd proxy header detection
Loop on wp-adminFORCE_SSL_ADMIN with HTTP accessEnable HTTPS or remove constant
Loop after migrationOld URLs in databaseRun WP-CLI search-replace

Emergency Recovery:

If locked out due to redirect loop, access via FTP/SSH and add to wp-config.php:

php
1// Temporary debugging - remove after fixing
2define('WP_HOME', 'http://your-actual-domain.com');
3define('WP_SITEURL', 'http://your-actual-domain.com');
4
5// Disable all redirects temporarily
6remove_action('template_redirect', 'redirect_canonical');

Mixed Content Warnings

After HTTPS migration, update:

bash
1# Update all internal URLs
2wp search-replace 'http://example.com' 'https://example.com' --all-tables
3
4# Check theme/plugin hardcoded URLs
5wp search-replace 'http:' 'https:' wp_posts wp_postmeta --dry-run

Mixed Content Detection Script:

bash
1#!/bin/bash
2# Find mixed content in database
3echo "Checking for HTTP references in wp_posts..."
4wp db query "SELECT ID, post_title FROM wp_posts WHERE post_content LIKE '%http://%'" --skip-column-names
5
6echo "Checking for HTTP references in wp_postmeta..."
7wp db query "SELECT post_id, meta_key FROM wp_postmeta WHERE meta_value LIKE '%http://%'" --skip-column-names
8
9echo "Checking for HTTP references in wp_options..."
10wp db query "SELECT option_name FROM wp_options WHERE option_value LIKE '%http://%'" --skip-column-names

Content Security Policy for Mixed Content:

apache
1# .htaccess - upgrade insecure requests
2<IfModule mod_headers.c>
3Header always set Content-Security-Policy "upgrade-insecure-requests"
4</IfModule>
nginx
1# nginx - upgrade insecure requests
2add_header Content-Security-Policy "upgrade-insecure-requests" always;

White Screen of Death (WSOD)

URL configuration errors can cause blank pages:

php
1// Add to wp-config.php for debugging
2define('WP_DEBUG', true);
3define('WP_DEBUG_LOG', true);
4define('WP_DEBUG_DISPLAY', false);
5
6// Check debug.log in wp-content/
7// tail -f wp-content/debug.log

Common WSOD Causes Related to URLs:

Error in LogCauseSolution
Cannot modify header informationOutput before redirectCheck for whitespace before <?php
Maximum execution time exceededInfinite redirect loopFix WP_HOME/WP_SITEURL mismatch
Allowed memory size exhaustedPlugin conflict during URL changeIncrease memory limit

Database Connection Issues After Migration

After migrating to new server with different database host:

php
1// Update database host
2define('DB_HOST', 'new-db-host.example.com');
3
4// For Docker internal networking
5define('DB_HOST', 'db'); // Service name in docker-compose
6
7// For socket connections
8define('DB_HOST', 'localhost:/var/run/mysqld/mysqld.sock');

Performance Impact

These constants bypass database queries, improving performance by ~2-5ms per request.

Performance Comparison:

ConfigurationDatabase QueriesResponse Time Impact
No constants (database lookup)2 queries per request+2-5ms
Constants defined0 queriesBaseline
With object cache0-2 queries (cached)+0.1-0.5ms

Benchmarking URL Resolution:

php
1// Add to theme's functions.php temporarily
2add_action('init', function() {
3 if (current_user_can('manage_options') && isset($_GET['benchmark_urls'])) {
4 $start = microtime(true);
5 for ($i = 0; $i < 1000; $i++) {
6 home_url('/');
7 site_url('/');
8 }
9 $end = microtime(true);
10 wp_die(sprintf('1000 URL resolutions: %.4f seconds', $end - $start));
11 }
12});
13// Access: yoursite.com?benchmark_urls=1

Best Practices

Configuration Management

  1. Always define both constants together to avoid inconsistencies
  2. Use environment variables for multi-environment deployments
  3. Document URL changes in version control comments
  4. Test after changes with wp option get home and wp option get siteurl
  5. Use HTTPS in production environments
  6. Backup database before direct SQL updates

Security Considerations

Hardening wp-config.php:

php
1// Prevent file editing from admin
2define('DISALLOW_FILE_EDIT', true);
3
4// Prevent plugin/theme installation
5define('DISALLOW_FILE_MODS', true);
6
7// Force SSL for admin and logins
8define('FORCE_SSL_ADMIN', true);
9
10// Limit post revisions
11define('WP_POST_REVISIONS', 5);
12
13// Disable WordPress cron (use system cron instead)
14define('DISABLE_WP_CRON', true);
15
16// Move wp-content directory (security through obscurity)
17// define('WP_CONTENT_DIR', '/path/to/custom/content');
18// define('WP_CONTENT_URL', 'https://example.com/custom/content');

File Permissions:

bash
1# Recommended permissions for WordPress files
2find /var/www/html -type d -exec chmod 755 {} \;
3find /var/www/html -type f -exec chmod 644 {} \;
4
5# Protect wp-config.php
6chmod 600 /var/www/html/wp-config.php
7
8# Make wp-content writable by web server
9chown -R www-data:www-data /var/www/html/wp-content

Multi-Environment Deployment

Environment Detection Pattern:

php
1/**
2 * Environment-based configuration
3 * Detects environment from hostname or environment variable
4 */
5
6$environment = getenv('WP_ENV') ?: 'production';
7
8// Auto-detect from hostname
9if (!getenv('WP_ENV')) {
10 $host = $_SERVER['HTTP_HOST'] ?? '';
11 if (strpos($host, 'localhost') !== false || strpos($host, '.local') !== false) {
12 $environment = 'development';
13 } elseif (strpos($host, 'staging') !== false || strpos($host, 'test') !== false) {
14 $environment = 'staging';
15 }
16}
17
18switch ($environment) {
19 case 'development':
20 define('WP_HOME', 'http://localhost:8080');
21 define('WP_SITEURL', 'http://localhost:8080');
22 define('WP_DEBUG', true);
23 define('WP_DEBUG_LOG', true);
24 define('WP_DEBUG_DISPLAY', true);
25 define('SCRIPT_DEBUG', true);
26 break;
27
28 case 'staging':
29 define('WP_HOME', 'https://staging.example.com');
30 define('WP_SITEURL', 'https://staging.example.com');
31 define('WP_DEBUG', true);
32 define('WP_DEBUG_LOG', true);
33 define('WP_DEBUG_DISPLAY', false);
34 break;
35
36 case 'production':
37 default:
38 define('WP_HOME', 'https://example.com');
39 define('WP_SITEURL', 'https://example.com');
40 define('WP_DEBUG', false);
41 define('DISALLOW_FILE_EDIT', true);
42 break;
43}

GitOps Configuration Pattern:

plaintext
1project/
2├── wp-config.php ← Base config (committed)
3├── wp-config-local.php ← Local overrides (gitignored)
4├── environments/
5│ ├── development.php
6│ ├── staging.php
7│ └── production.php
8└── .env.example ← Template for local .env
php
1// wp-config.php - load environment-specific config
2$environment = getenv('WP_ENV') ?: 'production';
3$env_config = __DIR__ . "/environments/{$environment}.php";
4
5if (file_exists($env_config)) {
6 require_once $env_config;
7}
8
9// Local overrides (not in version control)
10$local_config = __DIR__ . '/wp-config-local.php';
11if (file_exists($local_config)) {
12 require_once $local_config;
13}

Caching Considerations

Object Cache Integration:

php
1// Redis configuration
2define('WP_REDIS_HOST', 'redis');
3define('WP_REDIS_PORT', 6379);
4define('WP_REDIS_DATABASE', 0);
5
6// Memcached configuration
7$memcached_servers = [
8 ['memcached', 11211],
9];

Cache Invalidation After URL Change:

bash
1# Complete cache flush procedure
2wp cache flush
3wp transient delete --all
4wp rewrite flush
5
6# For popular caching plugins
7wp w3-total-cache flush all 2>/dev/null || true
8wp wp-super-cache flush 2>/dev/null || true
9
10# Clear object cache
11wp redis flush 2>/dev/null || true
12
13# Clear OPcache if available
14wp eval 'if (function_exists("opcache_reset")) opcache_reset();'

Monitoring and Validation

Health Check Script:

bash
1#!/bin/bash
2# WordPress URL Health Check
3
4SITE_URL=$(wp option get home --skip-plugins --skip-themes)
5EXPECTED_URL="https://example.com"
6
7echo "=== WordPress URL Health Check ==="
8echo "Home URL: $SITE_URL"
9echo "Expected: $EXPECTED_URL"
10
11# Check HTTP response
12HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$SITE_URL")
13echo "HTTP Status: $HTTP_CODE"
14
15# Check for redirect loops
16REDIRECT_COUNT=$(curl -s -o /dev/null -w "%{redirect_url}" -L --max-redirs 10 "$SITE_URL" 2>&1 | wc -l)
17echo "Redirects: $REDIRECT_COUNT"
18
19# Check SSL certificate
20if [[ "$SITE_URL" == https://* ]]; then
21 SSL_EXPIRY=$(echo | openssl s_client -servername "${SITE_URL#https://}" -connect "${SITE_URL#https://}:443" 2>/dev/null | openssl x509 -noout -enddate 2>/dev/null)
22 echo "SSL Expires: ${SSL_EXPIRY#notAfter=}"
23fi
24
25# Validate URLs match
26if [[ "$SITE_URL" == "$EXPECTED_URL" && "$HTTP_CODE" == "200" ]]; then
27 echo "Status: ✅ HEALTHY"
28 exit 0
29else
30 echo "Status: ❌ ISSUES DETECTED"
31 exit 1
32fi

Quick Reference

Essential Constants:

ConstantPurposeExample
WP_HOMEFront-end URLhttps://example.com
WP_SITEURLWordPress core URLhttps://example.com
FORCE_SSL_ADMINForce HTTPS for admintrue
COOKIE_DOMAINCookie domain scope.example.com

WP-CLI Quick Commands:

bash
1# View current URLs
2wp option get home
3wp option get siteurl
4
5# Update URLs
6wp option update home 'https://newdomain.com'
7wp option update siteurl 'https://newdomain.com'
8
9# Full migration with search-replace
10wp search-replace 'old.com' 'new.com' --all-tables --dry-run
11wp search-replace 'old.com' 'new.com' --all-tables
12
13# Flush all caches
14wp cache flush && wp rewrite flush

Emergency Recovery Steps:

  1. Access server via SSH/FTP
  2. Edit wp-config.php with correct WP_HOME and WP_SITEURL
  3. Clear .htaccess or rename to .htaccess.bak
  4. Access wp-admin and verify Settings → General
  5. Go to Settings → Permalinks and click "Save" to regenerate .htaccess
  6. Test site thoroughly