Description
Here we’ll do the following:
- Create a new database container
- Import and reconnect the database dump
- Move the old DocumentRoot (core WordPress files, uploads, themes and plugins) into the new DocumentRoot
- Edit the wp-config.php
Doing The Work
- Create a new database container and admin user for it in MariaDB or MySQL and import database dump into the new container.
- Login to MariaDB or MySQL
mysql -p
- Create a new database container
create database database_name;
- Create a new user for the database container
grant all privileges on database_name.* to 'database_admin'@'localhost' identified by 'strong_password';
- Exit MariaDB or MySQL Terminal
exit;
- Import database dump from Step 1 into the newly created database container
mysql -u username -ppassword database_name < database_name.sql
- Move DocumentRoot to the new location | This could require webserver setup and/or configuration
- Get free SSL certificates at StartSSL or the Let's Encrypt project, sponsored in part by Automattic and other companies.
- To avoid MITM (man-in-the-middle) attacks where a user hijacks the communication during the process, which is possible for a split second during a redirect see this article about HSTS (HTTP Strict Transport Security).
- To test your setup with the above configuration and SSLCipherSuite, go to https://www.ssllabs.com/ssltest/
- Read more about Apache httpd Redirect and RedirectMatch
- Edit wp-config.php | we'll need to change the database name, database user, database host, secret key salt table and add some WordPress PHP constants
- Generate new secret key salts and/or replace your old salts. Use the WordPress secret key salt generator.
- If you've used absolute URLs (even if you've used relative URLs chances are high there's still some absolute URLs in your database that are serialized), use one of the following to search/replace the old/new domain names in your database. Do not edit the database dump manually with a text editor or using sed, the URLs are stored in serialized arrays and will be corrupted.
Note: MariaDB is a fork of MySQL led by the original developers of MySQL. MariaDB is a drop in replacement for MySQL and uses the same command syntax most of the time, there are some differences as well as configuration files, but overall is a more stable choice as MySQL is now owned by Oracle and its future is uncertain with the FOSS community. MariaDB is intended to remain free under the GNU GPL.
Before we begin
You’ll need access to PHPMyAdmin or the command line of your Linux server and a database administrator account.
Don’t forget to read about serialized PHP arrays
To import the database dump from the command line on the TARGET (new) server, follow these steps:
Important security consideration: WordPress requires SELECT, INSERT, UPDATE and DELETE privileges to function normally, however some plugins, themes and some major updates may require table alteration or other functionality which would need elevated privileges. In most cases there is no need to limit these privileges but if you do, do so with caution. Attempting these updates without the proper privileges can damage or destroy your install.
note: There is no space between the p's above in -ppassword, this is intended and is not a typo
note: There is almost never a need to use flush privileges;
In order to improve performance. MariaDB/MySQL maintains an in-memory copy of the GRANT tables, so they do not require reading it from disk on every connection, every default database change and every query sent to the server. The mentioned command forces the reload of this cache by reading it directly from disk (or the filesystem cache).
However, its execution is unnecessary in most practical cases because, "If you modify the grant tables indirectly using account-management statements such as GRANT, REVOKE, SET PASSWORD, or RENAME USER, the server notices these changes and loads the grant tables into memory again immediately."
There leaves only one then reason to perform that reload operation manually: "you modify the grant tables directly using statements such as INSERT, UPDATE, or DELETE"
See this nginx WordPress recipe for getting your nginx server up and serving your WordPress site. Here's a link to the WordPress Codex article on nginx.
Here's a sample Apache httpd config that assumes you will be using SSL. Note to WPMU users: If your site is SSL enabled you must have a wildcard ssl certificate for *.example.com or you will run into verification problems with sub-sites.
Apache httpd Configuration
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | ########################## ### Apache httpd vhost ### ########################## <VirtualHost *:80> ## General setup for the default virtual host ## ServerAdmin root@localhost ServerName example.com ServerAlias www.example.com ## Log Files (not necessary, added for redundancy) ## ErrorLog logs/example.com-error_log CustomLog logs/example.com-access_log combined ## Permanent redirect to SSL, uncomment and use *one* of the following ## # Redirect permanent / https://example.com/ # RedirectMatch ^/(.*) https://example.org/$1 </VirtualHost> ############################## ### Apache httpd SSL vhost ### ############################## <VirtualHost *:443> ## General setup for the default SSL virtual host ## ServerAdmin root@localhost DocumentRoot /var/www/example.com ServerName example.com <directory /var/www/example.com/> DirectoryIndex index.php FallbackResource /index.php </Directory> <directory /var/www/example.com/wp-admin/> FallbackResource disabled </Directory> ## Enable HSTS (if you have problems with this setting, remove `includeSubdomains;` below ## Header always set Strict-Transport-Security "max-age=63072000; includeSubdomains; preload" ## Log Files ## ErrorLog logs/example.com_ssl_error_log TransferLog logs/example.com_ssl_transfer_log CustomLog logs/example.com_ssl_access_log combined LogLevel warn ## Enable/Disable SSL ## SSLEngine On ## SSL Protocol support ## SSLProtocol all -SSLv2 -SSLv3 ## SSL Cipher Suite ## SSLHonorCipherOrder On SSLCipherSuite TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA384:EECDH+aRSA+SHA256:EECDH+aRSA+RC4:EECDH:EDH+aRSA:EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:kEDH+AESGCM:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-DSS-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:AES256:AES128:AES:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4 ## Server Certificate ## SSLCertificateFile /etc/pki/tls/certs/example.com.ssl.crt ## Server Private Key ## SSLCertificateKeyFile /etc/pki/tls/private/example.com.nopass.key ## Certificate Authority (CA) ## SSLCACertificateFile /etc/pki/tls/certs/start-ssl-ca-sha2.pem ## Server Certificate Chain ## SSLCertificateChainFile /etc/pki/tls/certs/sub.class1.server.ca.pem <Directory /var/www/example.com/> <Files ~ "\.(cgi|shtml|phtml|php3?)$"> SSLOptions +StdEnvVars </Files> </Directory> <Directory "/var/www/example.com/cgi-bin"> SSLOptions +StdEnvVars </Directory> ## Compress HTML, CSS, JavaScript, Text, XML and fonts with mod_deflate ## AddOutputFilterByType DEFLATE application/javascript AddOutputFilterByType DEFLATE application/rss+xml AddOutputFilterByType DEFLATE application/vnd.ms-fontobject AddOutputFilterByType DEFLATE application/x-font AddOutputFilterByType DEFLATE application/x-font-opentype AddOutputFilterByType DEFLATE application/x-font-otf AddOutputFilterByType DEFLATE application/x-font-truetype AddOutputFilterByType DEFLATE application/x-font-ttf AddOutputFilterByType DEFLATE application/x-javascript AddOutputFilterByType DEFLATE application/xhtml+xml AddOutputFilterByType DEFLATE application/xml AddOutputFilterByType DEFLATE font/opentype AddOutputFilterByType DEFLATE font/otf AddOutputFilterByType DEFLATE font/ttf AddOutputFilterByType DEFLATE image/svg+xml AddOutputFilterByType DEFLATE image/x-icon AddOutputFilterByType DEFLATE text/css AddOutputFilterByType DEFLATE text/html AddOutputFilterByType DEFLATE text/javascript AddOutputFilterByType DEFLATE text/plain AddOutputFilterByType DEFLATE text/xml ## Remove browser bugs from really old browsers ## BrowserMatch ^Mozilla/4 gzip-only-text/html BrowserMatch ^Mozilla/4\.0[678] no-gzip BrowserMatch \bMSIE !no-gzip !gzip-only-text/html Header append Vary User-Agent ## Expires Caching ## ExpiresActive On ExpiresByType image/jpg "access plus 1 year" ExpiresByType image/jpeg "access plus 1 year" ExpiresByType image/gif "access plus 1 year" ExpiresByType image/png "access plus 1 year" ExpiresByType text/css "access plus 1 month" ExpiresByType application/pdf "access plus 1 month" ExpiresByType text/x-javascript "access plus 1 month" ExpiresByType application/javascript "access 1 month" ExpiresByType application/x-shockwave-flash "access plus 1 month" ExpiresByType image/x-icon "access plus 1 year" ExpiresDefault "access plus 9 days" ## Remove Browswer Entity Tags ## <FilesMatch "\.(ico|pdf|flv|jpg|jpeg|png|gif|js|css|swf)(\.gz)?$"> Header set Expires "Thu, 30 Dec 2021 21:00:00 GMT" Header unset ETag FileETag None </FilesMatch> ######################### ## Additional Security ## ######################### ## Apache httpd 2.2 ## ## Disable access to xmlrpc.php ## <Directory /var/www/example.com/> <Files xmlrpc.php> Order allow,deny Deny from all </Files> ## Disable access to wp-config.php ## <Files wp-config.php> Order allow,deny Deny from all </Files> ## Prevent .htaccess files from being spidered or viewed via a web browser ## <FilesMatch "^\.ht"> Order allow,deny Deny from all Satisfy all </FilesMatch> </Directory> ## Apache httpd 2.4 ## ## Disable access to xmlrpc.php ## <Directory /var/www/example.com/> <Files xmlrpc.php> Require all denied </Files> ## Disable access to wp-config.php ## <Files wp-config.php> Require all denied </Files> ## Prevent .htaccess files from being spidered or viewed via a web browser ## <Files ".ht*"> Require all denied </Files> </Directory> ## MSIE fix ## SetEnvIf User-Agent ".*MSIE.*" \ nokeepalive ssl-unclean-shutdown \ downgrade-1.0 force-response-1.0 ## Per-Server Logging ## CustomLog logs/ssl_request_log \ "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b" </VirtualHost> |
nginx Configuration (Read More)
openssl dhparam -out /etc/nginx/ssl/dhparam.pem 4096
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | ####################### ### nginx SSL vhost ### ####################### ## General setup for the default SSL virtual host ## server { listen 443; server_name .example.com; root /www/example.com/public; client_max_body_size 512M; ## SSL Configuration ## ssl on; ssl_certificate /etc/pki/tls/certs/example.com.ssl.crt; ssl_certificate_key /etc/pki/tls/private/example.com.nopass.key; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_prefer_server_ciphers on; ssl_ciphers TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA384:EECDH+aRSA+SHA256:EECDH+aRSA+RC4:EECDH:EDH+aRSA:EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:kEDH+AESGCM:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-DSS-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:AES256:AES128:AES:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4 ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; ssl_stapling on; ssl_stapling_verify on; ssl_dhparam ssl/dhparam.pem; ## Turn off promiscuous data ### add_header Strict-Transport-Security "max-age=31536000; includeSubdomains"; add_header X-Frame-Options DENY; add_header X-Content-Type-Options nosniff; ## Logs ## access_log /www/example.com/logs/example.com-access.log main; error_log /www/example.com/logs/example.com-error.log; ## Default location settings ## location / { index index.html index.htm index.php; try_files $uri $uri/ /index.php?$args; } ## Redirect server error pages to the static page /50x.html ## error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } error_page 404 /404.html; ## Pass the PHP scripts to FastCGI server (locally with unix: param to avoid network overhead) ## location ~ \.php$ { ## Prevent Zero-day exploit ## try_files $uri =404; ## NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini ## fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass unix:/var/run/php-fpm/www.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } ## Deny access to .htaccess files, if Apache's document root ## location ~ /\.ht { deny all; } ## Exclude favicon from the logs to avoid bloating when it's not available ## location /favicon.ico { log_not_found off; access_log off; } } |
More helpful resources
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | /** The name of the database for WordPress */ define('DB_NAME', 'database_name'); /** MySQL database username */ define('DB_USER', 'database_user'); /** MySQL database password */ define('DB_PASSWORD', 'strong_password'); /** MySQL hostname */ define('DB_HOST', 'mysqlserver.example.net'); Add these additional constants just above: /* That's all, stop editing! Happy blogging. */ /** Additional WordPress PHP Constants to help with moving, these values override the values in the database but do not change them **/ define('WP_HOME','http://example.com'); define('WP_SITEURL','http://example.com'); /** Optional, this will change the value of WP_SITEURL in the database to the address used to load the new site by hitting /wp-login.php directly **/ define('RELOCATE', true); /** If the site SSL enabled: **/ define('WP_HOME','https://example.com'); define('WP_SITEURL','https://example.com'); define('FORCE_SSL_LOGIN', true); define('FORCE_SSL_ADMIN', true); |
Additionally See: this useful guide and full explanation of the wp-config.php file & WordPress PHP Constants
Serialized Arrays
→ interconnectit's SearchReplaceDB tool
→ wp-cli's Search-Replace command
→ Better Search Replace WordPress Plugin
A example of a normal PHP array and a serialized PHP array from Andrew Nacin's site
1 2 3 4 5 | $data = array( 'apple', 'banana', 'orange' ); echo serialize( $data ); //Result is a string we can unserialize into an array: a:3:{i:0;s:5:"apple";i:1;s:6:"banana";i:2;s:6:"orange";} |
Let's examine what's going on:
Normal PHP array: array( 'apple', 'banana', 'orange' );
Serialized PHP array: a:3:{i:0;s:5:"apple";i:1;s:6:"banana";i:2;s:6:"orange";}
Component | Meaning |
---|---|
a | array |
: | separator |
3 | 3 elements in array |
{ | open array |
i | integer |
0 | integer value = 0 |
; | break |
s | string |
5 | 5 chars in string |
} | close array |