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):
- Constants in wp-config.php (
WP_HOME,WP_SITEURL) - Filtered values via
home_urlandsite_urlfilters - Database options (
home,siteurlinwp_optionstable) - 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.phpor as environment variables - Take precedence over
homeandsiteurloptions inwp_optionstable
Internal WordPress Functions:
| Function | Uses | Returns |
|---|---|---|
home_url() | WP_HOME | Front-end URL for visitors |
site_url() | WP_SITEURL | WordPress installation URL |
admin_url() | WP_SITEURL | Admin area URL (/wp-admin/) |
content_url() | WP_SITEURL | Content directory URL (/wp-content/) |
includes_url() | WP_SITEURL | Includes directory URL (/wp-includes/) |
plugins_url() | WP_SITEURL | Plugins 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
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:
1define('WP_HOME', 'https://example.com');2define('WP_SITEURL', 'https://example.com');File Location Reference:
1WordPress Root/2├── wp-config.php ← Define constants here3├── wp-settings.php ← Loaded after wp-config.php4├── wp-content/5├── wp-admin/6└── wp-includes/Placement in wp-config.php:
1<?php2// Database settings3define('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 Keys13define('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:
1define('WP_HOME', getenv('WP_HOME') ?: 'http://localhost');2define('WP_SITEURL', getenv('WP_SITEURL') ?: 'http://localhost');Docker Compose example:
1environment:2 WP_HOME: https://production.com3 WP_SITEURL: https://production.com4 WORDPRESS_CONFIG_EXTRA: |5 define('WP_HOME', getenv('WP_HOME'));6 define('WP_SITEURL', getenv('WP_SITEURL'));Complete Docker Compose Stack:
1version: '3.8'2
3services:4 wordpress:5 image: wordpress:6.4-php8.2-apache6 ports:7 - "8080:80"8 environment:9 WORDPRESS_DB_HOST: db10 WORDPRESS_DB_USER: wp_user11 WORDPRESS_DB_PASSWORD: ${DB_PASSWORD}12 WORDPRESS_DB_NAME: wordpress13 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/html23 - ./wp-content:/var/www/html/wp-content24 depends_on:25 - db26
27 db:28 image: mariadb:10.1129 environment:30 MYSQL_DATABASE: wordpress31 MYSQL_USER: wp_user32 MYSQL_PASSWORD: ${DB_PASSWORD}33 MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}34 volumes:35 - db_data:/var/lib/mysql36
37volumes:38 wordpress_data:39 db_data:Environment File (.env):
1# .env file for Docker Compose2SITE_URL=https://example.com3DB_PASSWORD=secure_password_here4DB_ROOT_PASSWORD=root_secure_password5WP_DEBUG=false6FORCE_SSL=trueKubernetes ConfigMap Example:
1apiVersion: v12kind: ConfigMap3metadata:4 name: wordpress-config5data: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:
1define('WP_HOME', 'https://example.com');2define('WP_SITEURL', 'https://example.com/wordpress');Directory Structure for Subdirectory Installation:
1public_html/ ← Document root (WP_HOME)2├── index.php ← Modified loader3├── .htaccess ← Rewrite rules4└── wordpress/ ← WordPress core (WP_SITEURL)5 ├── wp-admin/6 ├── wp-content/7 ├── wp-includes/8 ├── wp-config.php9 └── index.phpModified Root index.php:
1<?php2/**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:
1# BEGIN WordPress Subdirectory2<IfModule mod_rewrite.c>3RewriteEngine On4RewriteBase /5RewriteRule ^index\.php$ - [L]6
7# Don't rewrite requests to actual files or directories8RewriteCond %{REQUEST_FILENAME} !-f9RewriteCond %{REQUEST_FILENAME} !-d10
11# Rewrite everything else to index.php12RewriteRule . /index.php [L]13</IfModule>14# END WordPress SubdirectoryUse Cases for Subdirectory Installation:
| Scenario | WP_HOME | WP_SITEURL |
|---|---|---|
| Clean URLs (core hidden) | https://example.com | https://example.com/wp |
| Multiple apps on domain | https://example.com/blog | https://example.com/blog/wordpress |
| Development isolation | http://localhost:8080 | http://localhost:8080/wordpress |
Database Direct Update (Migration/Emergency)
When wp-config.php is inaccessible, update URLs via SQL:
1-- Update site URL2UPDATE wp_options3SET option_value = 'https://newdomain.com'4WHERE option_name IN ('home', 'siteurl');5
6-- Verify changes7SELECT option_name, option_value8FROM wp_options9WHERE 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:
1-- wp_options table structure2+-------------+---------------------+------+-----+---------+----------------+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:
1-- Start transaction for safety2START TRANSACTION;3
4-- Update core URL options5UPDATE wp_options6SET 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_posts11SET guid = REPLACE(guid, 'http://olddomain.com', 'https://newdomain.com');12
13-- Update post content (images, links)14UPDATE wp_posts15SET post_content = REPLACE(post_content, 'http://olddomain.com', 'https://newdomain.com');16
17-- Update post excerpts18UPDATE wp_posts19SET post_excerpt = REPLACE(post_excerpt, 'http://olddomain.com', 'https://newdomain.com');20
21-- Verify changes before committing22SELECT option_name, option_value FROM wp_options WHERE option_name IN ('home', 'siteurl');23
24-- Commit if everything looks correct25COMMIT;26
27-- Or rollback if issues found28-- ROLLBACK;Serialized Data Warning:
WordPress stores complex data as PHP serialized strings. Direct SQL replacement breaks serialization:
1// Example serialized data in wp_options2a:2:{s:4:"home";s:24:"http://olddomain.com";s:7:"version";s:5:"6.4.2";}3// ↑ ↑4// String length (24) Actual string5
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 MISMATCHThis is why WP-CLI search-replace is strongly recommended for production migrations.
WP-CLI Method
Recommended for migrations and server-side automation:
1# Update URLs2wp 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-only8
9# Flush cache and rewrite rules10wp cache flush11wp rewrite flushWP-CLI Search-Replace Options:
| Flag | Description |
|---|---|
--dry-run | Preview changes without applying |
--all-tables | Include non-WordPress tables |
--network | For multisite, run on all sites |
--skip-columns=<cols> | Skip specific columns (e.g., guid) |
--precise | Use PHP serialization for accuracy |
--report-changed-only | Only report tables with changes |
--log=<file> | Log changes to file |
Comprehensive Migration Script:
1#!/bin/bash2# WordPress URL Migration Script3# Usage: ./migrate-urls.sh OLD_URL NEW_URL4
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 backup14echo "[1/5] Creating database backup..."15wp db export "$BACKUP_FILE" --add-drop-table16
17# Dry run first18echo "[2/5] Analyzing changes (dry run)..."19wp search-replace "$OLD_URL" "$NEW_URL" \20 --all-tables \21 --dry-run \22 --report-changed-only23
24# Confirm before proceeding25read -p "Proceed with migration? (y/N): " confirm26if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then27 echo "Migration cancelled."28 exit 029fi30
31# Execute replacement32echo "[3/5] Executing URL replacement..."33wp search-replace "$OLD_URL" "$NEW_URL" \34 --all-tables \35 --precise \36 --report-changed-only37
38# Handle protocol changes (http → https)39if [[ "$NEW_URL" == https://* && "$OLD_URL" == http://* ]]; then40 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-only46fi47
48# Flush caches49echo "[4/5] Flushing caches..."50wp cache flush51wp transient delete --all52wp rewrite flush53
54# Verify55echo "[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:
1# List all sites in network2wp site list --fields=blog_id,url3
4# Migrate specific site5wp search-replace 'http://old.com' 'https://new.com' \6 --url=http://old.com \7 --all-tables-with-prefix8
9# Migrate entire network10wp search-replace 'http://old.com' 'https://new.com' \11 --network \12 --all-tablesCommon Scenarios
Multisite Network
Each site in a network requires individual URL configuration:
1// Network-wide constants2define('DOMAIN_CURRENT_SITE', 'example.com');3define('PATH_CURRENT_SITE', '/');4
5// Per-site URLs managed via wp_blogs tableMultisite Database Tables:
WordPress Multisite manages URLs across multiple tables:
1-- Main site configuration2SELECT * FROM wp_options WHERE option_name IN ('home', 'siteurl');3
4-- Network sites registry5SELECT 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 mapping11SELECT * FROM wp_domain_mapping;Network Administration Constants:
1// Multisite network configuration2define('WP_ALLOW_MULTISITE', true);3define('MULTISITE', true);4define('SUBDOMAIN_INSTALL', false); // false = subdirectory, true = subdomain5define('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 authentication11define('COOKIE_DOMAIN', '.example.com');12define('ADMIN_COOKIE_PATH', '/');13define('COOKIEPATH', '/');14define('SITECOOKIEPATH', '/');HTTPS/SSL Migration
When moving from HTTP to HTTPS:
1// Force HTTPS2define('FORCE_SSL_ADMIN', true);3
4// Update URLs to HTTPS5define('WP_HOME', 'https://example.com');6define('WP_SITEURL', 'https://example.com');Then update content URLs:
1wp search-replace 'http://example.com' 'https://example.com' --all-tablesSSL-Related Constants Reference:
| Constant | Purpose | Default |
|---|---|---|
FORCE_SSL_ADMIN | Force HTTPS for wp-admin and wp-login | false |
FORCE_SSL_LOGIN | Force HTTPS for login only (deprecated) | false |
Complete HTTPS Migration Checklist:
1// wp-config.php HTTPS configuration2define('FORCE_SSL_ADMIN', true);3define('WP_HOME', 'https://example.com');4define('WP_SITEURL', 'https://example.com');5
6// Force HTTPS for all content7if (strpos($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '', 'https') !== false) {8 $_SERVER['HTTPS'] = 'on';9}.htaccess HTTPS Redirect:
1# Force HTTPS redirect2<IfModule mod_rewrite.c>3RewriteEngine On4RewriteCond %{HTTPS} off5RewriteCond %{HTTP:X-Forwarded-Proto} !https6RewriteRule ^(.*)$ 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:
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 configuration15 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 # HSTS20 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):
1// Trust proxy headers2if (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:
1/**2 * Reverse Proxy / Load Balancer Configuration3 * Place before wp-settings.php require4 */5
6// Trust X-Forwarded-* headers from known proxies7$trusted_proxies = [8 '10.0.0.0/8', // Internal network9 '172.16.0.0/12', // Docker default10 '192.168.0.0/16', // Local network11 '127.0.0.1', // Localhost12];13
14// Check if request is from trusted proxy15$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 protocol34 if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {35 $_SERVER['HTTPS'] = 'on';36 }37
38 // Set real client IP39 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 host47 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:
1// Cloudflare IP ranges (update periodically from cloudflare.com/ips)2// Simplified version - use actual Cloudflare IPs in production3if (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:
1# nginx reverse proxy configuration2location / {3 proxy_pass http://wordpress-backend;4
5 # Forward original request info6 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 requests19 proxy_connect_timeout 60s;20 proxy_send_timeout 60s;21 proxy_read_timeout 60s;22}Traefik Labels for Docker:
1services:2 wordpress:3 image: wordpress:latest4 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:
.htaccessrewrite rules- Proxy/CDN configuration
$_SERVER['HTTPS']value
Debugging Redirect Loops:
1// Add to wp-config.php temporarily for debugging2error_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:
| Symptom | Cause | Solution |
|---|---|---|
| Loop on all pages | WP_HOME/WP_SITEURL mismatch with actual URL | Verify constants match access URL |
| Loop on HTTPS only | $_SERVER['HTTPS'] not set | Add proxy header detection |
| Loop on wp-admin | FORCE_SSL_ADMIN with HTTP access | Enable HTTPS or remove constant |
| Loop after migration | Old URLs in database | Run WP-CLI search-replace |
Emergency Recovery:
If locked out due to redirect loop, access via FTP/SSH and add to wp-config.php:
1// Temporary debugging - remove after fixing2define('WP_HOME', 'http://your-actual-domain.com');3define('WP_SITEURL', 'http://your-actual-domain.com');4
5// Disable all redirects temporarily6remove_action('template_redirect', 'redirect_canonical');Mixed Content Warnings
After HTTPS migration, update:
1# Update all internal URLs2wp search-replace 'http://example.com' 'https://example.com' --all-tables3
4# Check theme/plugin hardcoded URLs5wp search-replace 'http:' 'https:' wp_posts wp_postmeta --dry-runMixed Content Detection Script:
1#!/bin/bash2# Find mixed content in database3echo "Checking for HTTP references in wp_posts..."4wp db query "SELECT ID, post_title FROM wp_posts WHERE post_content LIKE '%http://%'" --skip-column-names5
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-names8
9echo "Checking for HTTP references in wp_options..."10wp db query "SELECT option_name FROM wp_options WHERE option_value LIKE '%http://%'" --skip-column-namesContent Security Policy for Mixed Content:
1# .htaccess - upgrade insecure requests2<IfModule mod_headers.c>3Header always set Content-Security-Policy "upgrade-insecure-requests"4</IfModule>1# nginx - upgrade insecure requests2add_header Content-Security-Policy "upgrade-insecure-requests" always;White Screen of Death (WSOD)
URL configuration errors can cause blank pages:
1// Add to wp-config.php for debugging2define('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.logCommon WSOD Causes Related to URLs:
| Error in Log | Cause | Solution |
|---|---|---|
Cannot modify header information | Output before redirect | Check for whitespace before <?php |
Maximum execution time exceeded | Infinite redirect loop | Fix WP_HOME/WP_SITEURL mismatch |
Allowed memory size exhausted | Plugin conflict during URL change | Increase memory limit |
Database Connection Issues After Migration
After migrating to new server with different database host:
1// Update database host2define('DB_HOST', 'new-db-host.example.com');3
4// For Docker internal networking5define('DB_HOST', 'db'); // Service name in docker-compose6
7// For socket connections8define('DB_HOST', 'localhost:/var/run/mysqld/mysqld.sock');Performance Impact
These constants bypass database queries, improving performance by ~2-5ms per request.
Performance Comparison:
| Configuration | Database Queries | Response Time Impact |
|---|---|---|
| No constants (database lookup) | 2 queries per request | +2-5ms |
| Constants defined | 0 queries | Baseline |
| With object cache | 0-2 queries (cached) | +0.1-0.5ms |
Benchmarking URL Resolution:
1// Add to theme's functions.php temporarily2add_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=1Best Practices
Configuration Management
- Always define both constants together to avoid inconsistencies
- Use environment variables for multi-environment deployments
- Document URL changes in version control comments
- Test after changes with
wp option get homeandwp option get siteurl - Use HTTPS in production environments
- Backup database before direct SQL updates
Security Considerations
Hardening wp-config.php:
1// Prevent file editing from admin2define('DISALLOW_FILE_EDIT', true);3
4// Prevent plugin/theme installation5define('DISALLOW_FILE_MODS', true);6
7// Force SSL for admin and logins8define('FORCE_SSL_ADMIN', true);9
10// Limit post revisions11define('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:
1# Recommended permissions for WordPress files2find /var/www/html -type d -exec chmod 755 {} \;3find /var/www/html -type f -exec chmod 644 {} \;4
5# Protect wp-config.php6chmod 600 /var/www/html/wp-config.php7
8# Make wp-content writable by web server9chown -R www-data:www-data /var/www/html/wp-contentMulti-Environment Deployment
Environment Detection Pattern:
1/**2 * Environment-based configuration3 * Detects environment from hostname or environment variable4 */5
6$environment = getenv('WP_ENV') ?: 'production';7
8// Auto-detect from hostname9if (!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:
1project/2├── wp-config.php ← Base config (committed)3├── wp-config-local.php ← Local overrides (gitignored)4├── environments/5│ ├── development.php6│ ├── staging.php7│ └── production.php8└── .env.example ← Template for local .env1// wp-config.php - load environment-specific config2$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:
1// Redis configuration2define('WP_REDIS_HOST', 'redis');3define('WP_REDIS_PORT', 6379);4define('WP_REDIS_DATABASE', 0);5
6// Memcached configuration7$memcached_servers = [8 ['memcached', 11211],9];Cache Invalidation After URL Change:
1# Complete cache flush procedure2wp cache flush3wp transient delete --all4wp rewrite flush5
6# For popular caching plugins7wp w3-total-cache flush all 2>/dev/null || true8wp wp-super-cache flush 2>/dev/null || true9
10# Clear object cache11wp redis flush 2>/dev/null || true12
13# Clear OPcache if available14wp eval 'if (function_exists("opcache_reset")) opcache_reset();'Monitoring and Validation
Health Check Script:
1#!/bin/bash2# WordPress URL Health Check3
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 response12HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$SITE_URL")13echo "HTTP Status: $HTTP_CODE"14
15# Check for redirect loops16REDIRECT_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 certificate20if [[ "$SITE_URL" == https://* ]]; then21 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=}"23fi24
25# Validate URLs match26if [[ "$SITE_URL" == "$EXPECTED_URL" && "$HTTP_CODE" == "200" ]]; then27 echo "Status: ✅ HEALTHY"28 exit 029else30 echo "Status: ❌ ISSUES DETECTED"31 exit 132fiQuick Reference
Essential Constants:
| Constant | Purpose | Example |
|---|---|---|
WP_HOME | Front-end URL | https://example.com |
WP_SITEURL | WordPress core URL | https://example.com |
FORCE_SSL_ADMIN | Force HTTPS for admin | true |
COOKIE_DOMAIN | Cookie domain scope | .example.com |
WP-CLI Quick Commands:
1# View current URLs2wp option get home3wp option get siteurl4
5# Update URLs6wp option update home 'https://newdomain.com'7wp option update siteurl 'https://newdomain.com'8
9# Full migration with search-replace10wp search-replace 'old.com' 'new.com' --all-tables --dry-run11wp search-replace 'old.com' 'new.com' --all-tables12
13# Flush all caches14wp cache flush && wp rewrite flushEmergency Recovery Steps:
- Access server via SSH/FTP
- Edit
wp-config.phpwith correctWP_HOMEandWP_SITEURL - Clear
.htaccessor rename to.htaccess.bak - Access wp-admin and verify Settings → General
- Go to Settings → Permalinks and click "Save" to regenerate
.htaccess - Test site thoroughly