WordPress is a well-known content management system, while Docker can package applications and their dependencies into a lightweight, portable container, which can then be deployed on any popular Linux machine. The combination of the two allows for website creation with just a few simple configuration steps.
Purchase Instances (Servers)#
Cloud servers can be purchased from places like Alibaba Cloud, Tencent Cloud, Huawei Cloud, Amazon Cloud, Google Cloud, etc. Among them, Google and Amazon offer one-year trial packages, while new users and campus users of Alibaba, Tencent, and Huawei also have many discounts. Friends can choose suitable packages to purchase.
Install Docker and Docker Compose#
You can directly choose an image with Docker when purchasing the server. If not, you can find the option to change the operating system (or change the image) in the console to switch to an image that includes Docker. If it’s still not available, you can install it using the following method:
# Install Docker
sudo curl -sSL https://get.daocloud.io/docker | sh
# Install Docker Compose
sudo curl -L "https://github.com/docker/compose/releases/download/v2.2.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
Create Persistent Directories#
Once a Docker container is destroyed, the data inside the container will be lost. To avoid this, we generally persist this data by mounting volumes:
# Switch to the installation directory
cd ~
# Create the necessary directories for mounting
## Create MySQL configuration directory
mkdir db
## Create MySQL data directory
mkdir db_data
## Create WordPress file directory
mkdir wordpress_data
## Create Nginx file directory
mkdir nginx
# Create essential configuration files
## Create MySQL configuration file
cat > ./db/mysql.cnf << EOF
[mysqld]
explicit_defaults_for_timestamp=true
wait_timeout=86400
interactive_timeout=7200
EOF
## Create Nginx configuration file
### First, start an Nginx image
sudo docker run -it -d --name nginx nginx:latest
### Copy the /etc/nginx directory from the container to local
sudo docker cp nginx:/etc/nginx ./nginx/conf
### Copy the /usr/share/nginx/html directory from the container to local
sudo docker cp nginx:/usr/share/nginx/html ./nginx/html
### Create Nginx log directory
mkdir ./nginx/log
### After copying files, remove the Nginx image
sudo docker stop nginx
sudo docker rm nginx
Configure docker-compose.yml#
vi docker-compose.yml
The file content is as follows:
version: '3'
services:
nginx:
image: nginx:latest
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf:/etc/nginx:ro
- ./nginx/html:/usr/share/nginx/html:rw
- ./nginx/log:/var/log/nginx:rw
- ./nginx/ssl:/etc/ssl:ro
wpdatabase:
image: mysql:latest
volumes:
- ./db_data:/var/lib/mysql
- ./db/mysql.cnf:/etc/my.cnf
restart: always
environment:
MYSQL_ROOT_PASSWORD: root # Database administrator password, recommended to change to a strong password
MYSQL_DATABASE: wordpress # WordPress database
MYSQL_USER: wordpress # WordPress user
MYSQL_PASSWORD: wordpress # Password for the WordPress user, recommended to change to a strong password
wordpress:
depends_on:
- wpdatabase
image: wordpress:latest
volumes:
- ./wordpress_data:/var/www/html
restart: always
environment:
WORDPRESS_DB_HOST: wpdatabase:3306
WORDPRESS_DB_USER: wordpress # MySQL user configured above
WORDPRESS_DB_PASSWORD: wordpress # Password for the MySQL user configured above
Start the Container#
sudo docker-compose up -d
Modify Nginx Configuration File#
After the above configuration, we can see that the container has started by using sudo docker ps
, but Nginx is not configured properly for proxying, so we cannot directly access the WordPress page yet. We just need to create a configuration file:
# Create Nginx configuration file
vi ./nginx/conf/conf.d/wordpress.conf
Press the i
key to enter edit mode, change the following content, and paste it:
server {
listen 80;
server_name x.x.x.x; # Change this to your host IP
location / {
proxy_pass http://wordpress;
proxy_redirect off;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Forward host information
proxy_set_header Host $host;
}
}
Press :
then type wq!
to save the file.
Restart Nginx Container and Access the Website#
sudo docker-compose restart nginx
At this point, you can directly access the server IP address to visit WordPress. If an error occurs, you can use tail -f ./nginx/log/error.log
to check the error log.
Advanced Configuration#
One-stop Website Building Service#
The above method can only deploy a WordPress application simply on the server. Others can access your page using the public IP, but this is not compliant and not secure. A complete website building process also includes domain registration, website filing, public security filing, DNS resolution, SSL certificate issuance, etc. All of these steps can be obtained as a one-stop service from cloud service providers. Domain registration is paid, while other steps have free options. It should be noted that website filing requires about 20 days for approval.
SSL Certificate Configuration#
After obtaining a free SSL certificate from the cloud service provider, upload it to the server and copy the .crt and .key files to the ./nginx/conf directory, and generate Mozilla's pem file:
# Copy SSL certificate files
cp /path/to/xxx.crt ./nginx/conf
cp /path/to/xxx.key ./nginx/conf
# Generate Mozilla's pem file
curl https://ssl-config.mozilla.org/ffdhe2048.txt > ./nginx/conf/dhparam.pem
Modify the ./nginx/conf/conf.d/wordpress.conf file according to the marked comments:
# generated 2022-03-24, Mozilla Guideline v5.6, nginx 1.17.7, OpenSSL 1.1.1k, intermediate configuration
# https://ssl-config.mozilla.org/#server=nginx&version=1.17.7&config=intermediate&openssl=1.1.1k&guideline=5.6
server {
listen 80;
listen [::]:80;
server_name _;
server_name yangziwen.cn www.yangziwen.cn; # Change this to your own domain
location / {
return 301 https://yangziwen.cn$request_uri; # Change this to your own domain
}
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name yangziwen.cn www.yangziwen.cn; # Change this to your own domain
location / {
proxy_pass http://wordpress;
proxy_http_version 1.1;
proxy_cache_bypass $http_upgrade;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
}
ssl_certificate xxx.crt; # Change this to your own SSL certificate
ssl_certificate_key xxx.key; # Change this to your own SSL certificate key
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
ssl_session_tickets off;
# curl https://ssl-config.mozilla.org/ffdhe2048.txt > /path/to/dhparam
ssl_dhparam dhparam.pem;
# intermediate configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# HSTS (ngx_http_headers_module is required) (63072000 seconds)
add_header Strict-Transport-Security "max-age=63072000" always;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";
# OCSP stapling
ssl_stapling on;
ssl_stapling_verify on;
# verify chain of trust of OCSP response using Root CA and Intermediate certs
ssl_trusted_certificate xxx.crt; # Change this to your own SSL certificate
# replace with the IP address of your resolver
resolver 8.8.8.8;
location ~* /xmlrpc.php {
deny all;
}
if ( $fastcgi_script_name ~ \..*\/.*php ) {
return 403;
}
if ($request_method !~ ^(GET|PUT|HEAD|POST|OPTIONS)$ ) {
return 444;
}
}
The above configuration template is generated by https://ssl-config.mozilla.org/ and has undergone some simple security hardening. Next, restart Nginx to directly access your site via HTTPS.
sudo docker-compose restart nginx
At this point, you can check your site's HTTPS rating at https://myssl.com/. The score for the above configuration is A+. If it is not, it may be because images or other resources were referenced via HTTP on the homepage; change them to HTTPS references.
Nginx Security Hardening#
In the SSL configuration, we have already made some hardening to Nginx. There are still some configurations that need to be made in ./nginx/conf/nginx.conf. Modify the file as follows, mainly to prohibit HTTP and direct IP access.
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Hide Nginx version number to improve security.
server_tokens off;
# Hide PHP version
fastcgi_hide_header X-Powered-By;
proxy_hide_header X-Powered-By;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
include /etc/nginx/conf.d/*.conf;
server {
listen 80 default;
server_name _;
return 444; # Prevent access to the website via IP
location ~* \.(rar|zip|gz|tar|tgz|tar.gz|7z|z|bz2|tar.bz2|sql|log|rar|ini|bak|conf|DS_Store|idea|swp|svn|entries|git|config)$ {
deny all;
}
autoindex off;
include vhost/*.conf;
}
}
Restart Nginx:
sudo docker-compose restart nginx
WordPress Security Hardening#
It is strongly recommended to install the Wordfence plugin immediately after setting up the site and enable Two-Factor Authentication. During the period of setting up the site and filing, I have already intercepted many attempts to log in to the admin account and access sensitive interfaces through the Wordfence plugin.
Server Security Hardening#
Generally, after purchasing a cloud server, the provider will offer basic security scanning. Newly created instances need to be updated promptly according to the scanning results to ensure that some sensitive software (like OpenSSH) is up to date to avoid vulnerabilities.
SSH is the most commonly used way to log into servers and is also one of the favorite entry points for hackers. After creating a new instance, we should quickly configure the following two points:
- Create a new key and associate the server instance with the key. The specific method can refer to the cloud service provider's instructions. After creating a new key, we no longer need to enter a password to log into the instance; we just need to configure our private key in the SSH client to connect to the instance. The only thing to note is to avoid the leakage of the private key. If the private key is leaked or lost, you need to promptly delete the original key in the cloud service's user management center and create a new key.
- Modify the server firewall. Enter the management page of the cloud server and modify the firewall to only allow access to ports 80, 443, and ICMP. For port 22, if it’s not too much trouble, it’s best to only leave the source IP address that needs SSH login. However, for users using China Telecom or China Mobile broadband, the IP may change after a period or after restarting the router. If SSH cannot connect, it is likely due to the IP address update. For users with a fixed IP range, you can define the IP range using a subnet mask, which is the /24, /16, /8 that follows the IP.
SEO Configuration#
After building our beloved website, we certainly hope to be seen by like-minded friends as soon as possible. At this time, SEO (Search Engine Optimization) plays a significant role. In WordPress, we can easily configure SEO through some plugins. I use the Yoast SEO plugin, and after installing it, you can follow the plugin's prompts to make some simple configurations.
Sitemap is a very important part of SEO. The sitemap feature of Yoast is a paid function, but we can install the WP Sitemap Page plugin to generate a simple sitemap, which can be accessed via domain/sitemap_index.xml
. This is an index file, and when opened, it will have various types of indexes, each containing corresponding URL links, covering basically all the content displayed externally on our website. By submitting these XML indexes to the webmaster tools of Baidu, Google, and Bing, we can allow search engines to quickly index our site. Here are the addresses for these webmaster tools:
- Google: https://www.google.com/intl/zh-CN/webmasters/
- Baidu: https://ziyuan.baidu.com/site/index
- Bing: https://www.bing.com/webmasters/about?setlang=zh-cn
The configuration method is also very simple. First is site association, with various association methods; just choose one according to the instructions. Secondly, each webmaster tool has a sitemap upload feature. Google and Bing can be seen in the menu bar, while Baidu has a sitemap upload option under ordinary indexing. Just submit all the XML links in sitemap_index.xml.
After submitting the sitemap, just wait for the major search engines to crawl the URLs in the sitemap. I must praise Google's speed; the next day, I could find my website's content on the PC side, while Baidu took a bit longer.
robots.txt is a crawling protocol that tells search engine crawlers which pages can be crawled and which cannot. A good robots.txt can help crawlers quickly discover the pages you want to display. WordPress has a default robots.txt that includes a simple strategy to block wp-admin. We can add the sitemap address to help crawlers discover the sitemap faster. First, enter ./wordpress_data
to create a robots.txt
file, adding Sitemap: https://domain/sitemap_name.xml
, then access https://domain/robots.txt
to make it effective.