There are countless ways to install WordPress, and at the time of writing, Google has 488,000 entries for “WordPress installation.” That said, there are few tutorials that fully explain how to install WordPress and the underlying operating system in a way that supports long-term maintenance. The reason may be that the correct configuration is largely dependent on specific requirements, or it may be that a comprehensive installation tutorial cannot be explained in a few simple sentences.

In this article, we’ll try to address this. We provided bash scripts to automate WordPress installations on Ubuntu and explained in detail what each part did and what trade-offs we considered. (If you’re an advanced user, skip this article and go straight to the script section, download it, and modify it for your environment.) The final WordPress installation file is a writable script that supports Let’s Encrypt, uses NGINX Unit, and has been set up for production environments.

We continue with the basic architecture of deploying WordPress through NGINX Unit as described in the last blog post, plus installing and configuring features not covered in the last blog post (or many other tutorials) :

  • WordPress CLI
  • Let’s Encrypt and TLS/SSL certificates
  • Let’s Encrypt certificate automatically updates
  • NGINX cache
  • NGINX compression
  • NGINX HTTP/HTTPS and 2
  • Process automation

This article describes how to set up WordPress on a single node that hosts both a static resource Web server, a PHP processing server, and a database. In the future, we’ll cover multi-host, multi-service WordPress configuration installations. What other topics would you like us to discuss? Let us know in the comments section below!


The premise condition

  • Compute instance: Container (LXC or LXD, VIRTUAL machine or bare metal machine, with at least 512 MB of available memory, running Ubuntu 18.04 or later
  • Ports 80 and 443 of the compute instance are open to the Internet
  • The domain name that has been associated with the public IP address of the compute instance
  • Root or equivalent access obtained through sudo

The architecture overview

The application architecture is a three-tier Web application, the same architecture described in the last blog post. These include PHP scripts that must be executed by the PHP processor and static files that must be passed by the Web server.


General principles

  • To achieve idempotent, many of the configuration commands in the script are contained in conditional statements (if statements), : the script can be run multiple times without changing the correct Settings.
  • Scripts prefer to install software through a repository to enable you to install system security patches with a single command, such as Ubuntu’s apt Upgrade.
  • These commands detect whether their running environment is a container and make configuration changes accordingly.
  • When specifying the number of processes or threads to run in the configuration, the script provides the best automatic configuration parameters for containers, virtual machines, or bare metal machines (NGINX Unit on line 278, NGINX on line 306).
  • We write configurations using an automation-first approach that hopefully will provide a reference model for creating your own reusable infrastructure, code.
  • All commands are run as root because they change the core system configuration, but WordPress is run as a regular user at runtime.

Setting environment Variables

Set the following environment variables before running the script.

  • WORDPRESS_DB_PASSWORD – Password for logging in to the WordPress database.
  • WORDPRESS_ADMIN_USER – Username of the WordPress administrator.
  • WORDPRESS_ADMIN_PASSWORD – Password of the WordPress administrator.
  • WORDPRESS_ADMIN_EMAIL – The email address of the WordPress administrator.
  • WORDPRESS_URL – The full URL of a WordPress site, starting with https://.
  • LETS_ENCRYPT_STAGING – Empty by default, but set to 1 if Lets Encrypt is used to hold the staging server, which is required when testing new deployment configurations frequently. Otherwise, Let’s Encrypt may temporarily intercept your IP address because of the high number of requests.

The script checks to see if any wordpress-related variables are set, and if not, exits (lines 8-42, not shown here). Lines 572-576 check the LETS_ENCRYPT_STAGING value.


Set the derived environment variable

The script (lines 55-61, not shown here) sets the following environment variables to hard-coded values or derived values of the variables set in the previous section.

  • DEBIAN_FRONTEND=”noninteractive” – Indicates that the application automation script is executing commands and user interaction cannot be performed.
  • WORDPRESS_CLI_VERSION=”2.4.0″ – Downloaded version of the WordPress CLI.
  • WORDPRESS_CLI_MD5 = “dedd5a662b80cda66e9e25d44c23b25c” – WordPress CLI 2.4.0 binaries (WORDPRESS_CLI_VERSION variables specified version) encrypted checksum. Line 162 uses this value to verify that the downloaded WordPress CLI version is correct.
  • UPLOAD_MAX_FILESIZE=”16M” – Maximum size of a file to be uploaded by WordPress. This setting is used in multiple places in the configuration and can be defined centrally.
  • TLS_HOSTNAME = “(echo (echo (echo {WORDPRESS_URL} | the cut – d ‘/’ – the f3)” – extracted from WORDPRESS_URL variable system addressable host name. Used to obtain the appropriate TLS/SSL certificate from Lets Encrypt, as well as the ping operation for WordPress itself (see adding the WordPress Website host name to /etc/hosts).
  • NGINX_CONF_DIR=”/etc/nginx” – Directory path containing nginx configuration and the main configuration file nginx.conf.
  • CERT_DIR=”/etc/letsencrypt/live/${TLS_HOSTNAME}” – host name of the WordPress website (derived from the TLS_HOSTNAME variable) Let’s path to the Encrypt certificate.

Assign the WordPress site hostname to the compute instance

The script sets the host name of the compute instance to match the domain name of the WordPress site. This setting is not required for all configurations, but can be very helpful in single-host Settings (such as those for script configurations) when sending outbound E-mail over SMTP.

 63 # Change the hostname to be the same as the WordPress hostname
 64 if[!"$(hostname)"= ="${TLS_HOSTNAME}" ]; then 
 65 echo ▶ Changing hostname to ${TLS_HOSTNAME}"
 66 hostnamectl set-hostname "${TLS_HOSTNAME}"
 67 fi
Copy the code

Add the WordPress site host name to /etc/hosts

WordPress uses the WP-cron plugin to run scheduled tasks, provided that WordPress can ping through HTTP. To ensure wP-cron works in all environments, the script adds an entry to /etc/hosts that allows WordPress to route to itself via the local loopback interface.

 69 # Add the hostname to /etc/hosts
 70 if [ "$(grep -m1 "${TLS_HOSTNAME}" /etc/hosts)" = "" ]; then
 71 echo ▶ Adding hostname ${TLS_HOSTNAME} to /etc/hosts so that WordPress can ping itself"
 72 printf ": : 1% s \ n127.0.0.1% s \ n" "${TLS_HOSTNAME}" "${TLS_HOSTNAME}" >> /etc/hosts
 73 fi 
Copy the code

Installation tools required for subsequent steps

Later sections of the script use some of the utilities and assume that the repository index has been updated. We update the repository index (line 77) and immediately install the required tools (lines 78-84).

 75 # Make sure tools needed for install are present
 76 echo "▶ Installing prerequisite tools"
 77 apt-get -qq update
 78 apt-get -qq install -y \
 79 bc \
 80 ca-certificates \
 81 coreutils \
 82 curl \
 83 gnupg2 \
 84 lsb-release
Copy the code

Add NGINX Unit and NGINX open source libraries

The script installs NGINX Unit and the NGINX open source library through the official NGINX repository to ensure that we always have the latest security updates and patch fixes.

Here, the script installs the NGINX Unit library (lines 87-91) and the NGINX open source library (lines 94-98) by adding signing keys to the system and files for the APT configuration (which defines repository locations on the Internet).

The installation of NGINX Unit and the NGINX open source library actually starts in the next section. To avoid multiple updates to the metadata, we pre-added the repository to speed up the overall installation.

 86 # Install NGINX Unit repository
 87 if [ ! -f /etc/apt/sources.list.d/unit.list ]; then
 88 echo "▶ Installing NGINX Unit repository"
 89 curl -fsSL https://nginx.org/keys/nginx_signing.key | apt-key add -
 90 echo "deb https://packages.nginx.org/unit/ubuntu/ $(lsb_release -cs) unit" > /etc/apt/sources.list.d/unit.list
 91 fi
 92
 93 # Install NGINX repository
 94 if [ ! -f /etc/apt/sources.list.d/nginx.list ]; then
 95 echo "▶ Installing NGINX repository." "
 96 curl -fsSL https://nginx.org/keys/nginx_signing.key | apt-key add -
 97 echo "deb https://nginx.org/packages/mainline/ubuntu $(lsb_release -cs) nginx" > /etc/apt/sources.list.d/nginx.list
 98 fi
Copy the code

Install NGINX, NGINX Unit, PHP MariaDB, Certbot (Let’s Encrypt), and Dependencies.

After installing all repositories, we will update the repository metadata and install the application. The script installation package includes the PHP extensions recommended when running WordPress.

100 echo "▶ Updating the repository metadata"
101 apt-get -qq update
102

111 # Install PHP with dependencies and NGINX Unit
112 echo "▶ Installing PHP, NGINX Unit, NGINX, Certbot, and MariaDB"
113 apt-get -qq install -y --no-install-recommends \
114 certbot \
115 python3-certbot-nginx \
116 php-cli \
117 php-common \
118 php-bcmath \
119 php-curl \
120 php-gd \
121 php-imagick \
122 php-mbstring \
123 php-mysql \
124 php-opcache \
125 php-xml \
126 php-zip \
127 ghostscript \
128 nginx \
129 unit \
130 unit-php \
131 mariadb-server 
Copy the code

Configure PHP for NGINX Unit and WordPress

The script creates a configuration file in the PHP conf.d directory (lines 136-174). This file sets the maximum size for PHP files to upload (line 142), directs PHP errors to STDERR (line 145) so they can be logged in the NGINX Unit log, and restarts NGINX Unit (line 151).

133 # Find the major and minor PHP version so that we can write to its conf.d directory
134 PHP_MAJOR_MINOR_VERSION="$(PHP - v | head - n1 | the cut - d '- f2 | the cut - d'. '- f1, 2)"
135
136 if [ ! -f "/etc/php/${PHP_MAJOR_MINOR_VERSION}/embed/conf.d/30-wordpress-overrides.ini" ]; then
137 echo ▶ Configuring PHP for Use with NGINX Unit and WordPress
138 # Add PHP configuration overrides
139 cat > "/etc/php/${PHP_MAJOR_MINOR_VERSION}/embed/conf.d/30-wordpress-overrides.ini" << EOM
140 ; Set a larger maximum upload size so that WordPress can handle
141 ; bigger media files.
142 upload_max_filesize=${UPLOAD_MAX_FILESIZE}
143 post_max_size=${UPLOAD_MAX_FILESIZE}
144 ; Write error log to STDERR so that error messages show up in the NGINX Unit log
145 error_log=/dev/stderr
146 EOM
147 fi
148
149 # Restart NGINX Unit because we have reconfigured PHP
150 echo "▶ Restarting NGINX Unit"
151 service unit restart
Copy the code

Initialize the WordPress MariaDB database

We chose to use MariaDB instead of MySQL as our WordPress database. MariaDB has a more active open source community behind it and arguably provides better performance out of the box.

The script initializes the new database and creates credentials for WordPress to access via the local loopback address.

153 # Set up WordPress database
154 echo ▶ Configuring MariaDB for WordPress
155 mysqladmin create wordpress || echo "Ignoring above error because database may already exist"
156 mysql -e "GRANT ALL PRIVILEGES ON wordpress.*TO \"wordpress\"@\"localhost\" IDENTIFIED BY \"$WORDPRESS_DB_PASSWORD\"; FLUSH PRIVILEGES;"
Copy the code

Install the WordPress CLI utility

The script will now install the WP-CLI utility. Installing and managing WordPress using this program gives you complete control over your WordPress configuration without manually modifying files, updating databases, or navigating to the WordPress administrator panel. You can also use this program to install themes or plug-ins and upgrade WordPress.

158 if [ ! -f /usr/local/bin/wp ]; then
159 # Install the WordPress CLI
160 echo "▶ Installing the WordPress CLI tool"
161 curl --retry 6 -Ls "https://github.com/wp-cli/wp-cli/releases/download/v${WORDPRESS_CLI_VERSION}/wp-cli-${WORDPRESS_CLI_VERSION}.phar" > /usr/local/bin/wp
162 echo "$WORDPRESS_CLI_MD5 /usr/local/bin/wp" | md5sum -c -
163 chmod +x /usr/local/bin/wp
164 fi
Copy the code

Install and configure WordPress

The script will install the latest version of WordPress in the /var/www/wordpress directory and make the following Settings:

  • The database connects over the Unix domain socket loopback interface instead of the TCP loopback interface to reduce TCP traffic (line 175).
  • When a client connects to NGINX over HTTPS, WordPress adds the https:// prefix to the URL and passes the remote hostname (provided by NGINX) to PHP. We use a piece of PHP code (lines 180-189) for this configuration.
  • WordPress requires HTTPS login (line 194).
  • The default URL structure for WordPress is resource-based (line 201).
  • Set the correct file system permissions for the WordPress directory (lines 207-210).
166 if [ ! -d /var/www/wordpress ]; then
167 # Create WordPress directories
168 mkdir -p /var/www/wordpress
169 chown -R www-data:www-data /var/www
170
171 # Download WordPress using the WordPress CLI
172 echo "▶ Installing WordPress"
173 su -s /bin/sh -c 'wp --path=/var/www/wordpress core download' www-data
174
175 WP_CONFIG_CREATE_CMD="wp --path=/var/www/wordpress config create --extra-php --dbname=wordpress --dbuser=wordpress --dbhost=\"localhost:/var/run/mysqld/mysqld.sock\" --dbpass=\"${WORDPRESS_DB_PASSWORD}\""
176 
177 # This snippet is injected into the wp-config.php file when it is created.
178 # It informs WordPress that we are behind a reverse proxy and as such
179 # allow it to generate links using https.
180 cat > /tmp/wp_forwarded_for.php << 'EOM'
181 /* Turn HTTPS 'on' if HTTP_X_FORWARDED_PROTO matches 'https' */
182 if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strpos($_SERVER['HTTP_X_FORWARDED_PROTO'].'https')! = =false) {
183 $_SERVER['HTTPS'] = 'on';
184 }
185
186 if (isset($_SERVER['HTTP_X_FORWARDED_HOST']) {187 $_SERVER['HTTP_HOST'] = $_SERVER['HTTP_X_FORWARDED_HOST'];
188 }
189 EOM
190 
191 # Create WordPress configuration
192 su -s /bin/sh -p -c "cat /tmp/wp_forwarded_for.php | ${WP_CONFIG_CREATE_CMD}" www-data
193 rm /tmp/wp_forwarded_for.php
194 su -s /bin/sh -p -c "wp --path=/var/www/wordpress config set 'FORCE_SSL_ADMIN' 'true'" www-data
195
196 # Install WordPress
197 WP_SITE_INSTALL_CMD="wp --path=/var/www/wordpress core install --url=\"${WORDPRESS_URL}\" --title=\"${WORDPRESS_SITE_TITLE}\" --admin_user=\"${WORDPRESS_ADMIN_USER}\" --admin_password=\"${WORDPRESS_ADMIN_PASSWORD}\" --admin_email=\"${WORDPRESS_ADMIN_EMAIL}\" --skip-email"
198 su -s /bin/sh -p -c "${WP_SITE_INSTALL_CMD}" www-data
199
200 # Set permalink structure to a sensible default that isn't in the UI
201 su -s /bin/sh -p -c "wp --path=/var/www/wordpress option update permalink_structure '/%year%/%monthnum%/%postname%/'" www-data
202
203 # Remove sample file because it is cruft and could be a security problem
204 rm /var/www/wordpress/wp-config-sample.php
205
206 # Ensure that WordPress permissions are correct
207 find /var/www/wordpress -type d -exec chmod g+s {} \;
208 chmod g+w /var/www/wordpress/wp-content
209 chmod -R g+w /var/www/wordpress/wp-content/themes
210 chmod -R g+w /var/www/wordpress/wp-content/plugins
211 fi
Copy the code

Configure NGINX Unit

The script configures NGINX Unit to run PHP and handle WordPress paths, isolate PHP process namespaces, and tune performance Settings. Note the following three features:

  1. Namespaces support conditional definition based on whether a script is running in a container (lines 213-224). This definition is necessary because most container configurations do not support running other containers inside them.

  2. When namespace support is enabled, the Network namespace is disabled (line 218). To enable WordPress to access its own endpoints and connect to the Internet, the network namespace must be disabled.

  3. Use the following algorithm to calculate the maximum number of processes (lines 226-228) :(free memory to run MariaDB and NGINX Unit)/(PHP memory limit + 5). Then set this value in the NGINX Unit configuration on lines 277-280.

This value ensures that there are at least two PHP processes running at all times, which is important because WordPress itself makes multiple asynchronous calls, and operations such as WP-cron will fail if no other processes are running. Because the Settings generated here are conservative, you may need to increase or decrease this setting depending on your specific WordPress configuration. Many production system Settings are typically between 10 and 100.

213 if [ "${container:-unknown}"! ="lxc" ] && [ "$(grep -m1 -a container=lxc /proc/1/environ | tr -d '\0')"= ="" ]; then
214 NAMESPACES='"namespaces": {
215 "cgroup": true,
216 "credential": true,
217 "mount": true,
218 "network": false,
219 "pid": true,
220 "uname": true
221 }'
222 else
223 NAMESPACES='"namespaces": {}'
224 fi
225
226 PHP_MEM_LIMIT="$(grep 'memory_limit'/etc/PHP / 7.4 / embed PHP ini '| | tr - d cut - f2 - d = | numfmt - from = iec)"
227 AVAIL_MEM="$(grep MemAvailable /proc/meminfo | tr -d ' kB' | cut -f2 -d: | numfmt --from-unit=K)"
228 MAX_PHP_PROCESSES="$(echo "${AVAIL_MEM}/${PHP_MEM_LIMIT}+5" | bc)"
229 echo ▶ Calculated the maximum number of PHP processes as ${MAX_PHP_PROCESSES}.You may want to tune this value due to variations in your configuration.It is not unusual to see values between 10-100 in production configurations." 
230 
231 echo ▶ NGINX Unit to Use PHP and WordPress
232 cat > /tmp/wordpress.json << EOM
233 {
234 "settings": {
235 "http": {
236 "header_read_timeout": 30.237 "body_read_timeout": 30.238 "send_timeout": 30.239 "idle_timeout": 180.240 "max_body_size": $(numfmt --from=iec ${UPLOAD_MAX_FILESIZE})
241 }
242 },
243 "listeners": {
244 "127.0.0.1:8080": {
245 "pass": "routes/wordpress"
246 }
247 },
248 "routes": {
249 "wordpress": [
250 {
251 "match": {
252 "uri": [
253 "*.php".254 "*.php/*".255 "/wp-admin/"
255  ]
257 },
258 "action": {
259 "pass": "applications/wordpress/direct"
260 }
261 },
262 {
263 "action": {
264 "share": "/var/www/wordpress".265 "fallback": {
266 "pass": "applications/wordpress/index"
267 }
268 }
269 }
270 ]
271 },
272 "applications": {
273 "wordpress": {
274 "type": "php".275 "user": "www-data".276 "group": "www-data".277 "processes": {
278 "max": ${MAX_PHP_PROCESSES},
279 "spare": 1
280 },
281  "isolation": {
282 ${NAMESPACES}
283 },
284 "targets": {
285 "direct": {
286 "root": "/var/www/wordpress/"
287 },
288 "index": {
289 "root": "/var/www/wordpress/".290 "script": "index.php"
291 }
292 }
293 }
294 }
295 }
296 EOM
297 
298 curl -X PUT --data-binary @/tmp/wordpress.json --unix-socket /run/control.unit.sock http://localhost/config 
Copy the code

Configure NGINX

  • Configure core NGINX parameters.
  • Configure NGINX compression Settings
  • Configure NGINX parameters for WordPress

Configure core NGINX parameters

This script creates a directory for the NGINX cache directory (line 301) and the NGINX nginx.conf main configuration file (lines 304 — 341). The configuration Settings include the number of NGINX worker processes (line 306) and the maximum volume of uploaded files (line 325). Line 329 imports the compression configuration defined in the next section, and lines 332-337 set the cache parameters.

300 # Make directory for NGINX cache
301 mkdir -p /var/cache/nginx/proxy
302 
303 echo "▶ you NGINX." "
304 cat > ${NGINX_CONF_DIR}/nginx.conf << EOM
305 user nginx;
306 worker_processes auto;
307
308 error_log /var/log/nginx/error.log warn;
309 pid /var/run/nginx.pid;
310
311 events {
312 worker_connections 1024;
313 }
314
315 http {
316 include ${NGINX_CONF_DIR}/mime.types;
317 default_type application/octet-stream;
318
319 log_format main '\$remote_addr - \$remote_user [\$time_local] "\$request" '
320 '\$status \$body_bytes_sent "\$http_referer" '
321 '"\$http_user_agent" "\$http_x_forwarded_for"';
322
323 access_log /var/log/nginx/access.log main;
324 sendfile on;
325 client_max_body_size ${UPLOAD_MAX_FILESIZE};
326 keepalive_timeout 65;
327
328 # GZIP settings
329 include ${NGINX_CONF_DIR}/gzip_compression.conf;
330
331 # Cache settings
332 proxy_cache_path /var/cache/nginx/proxy
333 levels=1:2
334 keys_zone=wp_cache:10m
335 max_size=10g
336 inactive=60m
337 use_temp_path=off;
338
339 include ${NGINX_CONF_DIR}/conf.d/*.conf;
340 }
341 EOM
Copy the code

Configure NGINX compression Settings

Compressing content dynamically before sending it to the client can improve performance, but only if the compression configuration is correct. The script uses the configuration in the H5BP repository on GitHub (lines 346-414).

343 cat > ${NGINX_CONF_DIR}/gzip_compression.conf << 'EOM'
344 # Credit: https://github.com/h5bp/server-configs-nginx/
345
346# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --347 # | Compression |
348# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --349 
350 # https://nginx.org/en/docs/http/ngx_http_gzip_module.html
351 
352 # Enable gzip compression.
353 # Default: off
354 gzip on;
355
356 # Compression level (1-9).
357 # 5 is a perfect compromise between size and CPU usage, offering about 75%
358 # reduction for most ASCII files (almost identical to level 9).
359 # Default: 1
360 gzip_comp_level 6;
361
362 # Don't compress anything that's already small and unlikely to shrink much if at
363 # all (the default is 20 bytes, which is bad as that usually leads to larger
364 # files after gzipping).
365 # Default: 20
366 gzip_min_length 256;
367
368 # Compress data even for clients that are connecting to us via proxies,
369 # identified by the "Via" header (required for CloudFront).
370 # Default: off
371 gzip_proxied any;
372
373 # Tell proxies to cache both the gzipped and regular version of a resource
374 # whenever the client's Accept-Encoding capabilities header varies; 375 # Avoids the issue where a non-gzip capable client (which is extremely rare 376 # today) would display gibberish if their proxy gave them the gzipped version. 377 # Default: off 378 gzip_vary on; 379 380 # Compress all output labeled with one of the following MIME-types. 381 # `text/html` is always compressed by gzip module. 382 # Default: text/html 383 gzip_types 384 application/atom+xml 385 application/geo+json 386 application/javascript 387 application/x-javascript 388 application/json 389 application/ld+json 390 application/manifest+json 391 application/rdf+xml 392 application/rss+xml 393 application/vnd.ms-fontobject 394 application/wasm 395 application/x-web-app-manifest+json 396 application/xhtml+xml 397 application/xml 398 font/eot 399 font/otf 400 font/ttf  401 image/bmp 402 image/svg+xml 403 text/cache-manifest 404 text/calendar 405 text/css 406 text/javascript 407 text/markdown 408 text/plain 409 text/xml 410 text/vcard 411 text/vnd.rim.location.xloc 412 text/vtt 413 text/x-component 414 text/x-cross-domain-policy; 415 EOMCopy the code

Configure NGINX parameters for WordPress

Next, the script creates an NGINX configuration file named default.conf in conf.d and sets up WordPress (lines 417 — 541). The configuration is as follows:

  • Enable TLS certificates obtained from Let’s Encrypt via Certbot (lines 445-447; For Certbot configuration, see the next section).
  • Enforce TLS security Settings defined as best practices by Let’s Encrypt (lines 449-454)
  • By default, proxy requests are enabled for 1 hour of caching (line 458)
  • Disable access logging for two common request files, favicon.ico and robots.txt, and error logging when assets are not found (lines 465-474)
  • Deny access to hidden files and certain.php files to prevent illegal access or unintentional execution (lines 476-500)
  • Disable access logging for static file content and font files (lines 503-510)
  • Set the access-Control-Allow-Origin header for the font file (line 508)
  • Apply routing Settings to index.php files and other static files (lines 512-539)
417 cat > ${NGINX_CONF_DIR}/conf.d/default.conf << EOM
418 upstream unit_php_upstream {
419 server 127.0. 01.:8080;
420 
421 keepalive 32;
422 }
423
424 server {
425 listen 80;
426 listen [::]:80;
427
428 # ACME-challenge used by Certbot for Let's Encrypt 429 location ^~ /.well-known/acme-challenge/ { 430 root /var/www/certbot; 431 } 432 433 location / { 434 return 301 https://${TLS_HOSTNAME}\$request_uri; 435 } 436 } 437 438 server { 439 listen 443 ssl http2; 440 listen [::]:443 ssl http2; 441 server_name ${TLS_HOSTNAME}; 442 root /var/www/wordpress/; 443 444 # Let's Encrypt configuration
445 ssl_certificate ${CERT_DIR}/fullchain.pem;
446 ssl_certificate_key ${CERT_DIR}/privkey.pem;
447 ssl_trusted_certificate ${CERT_DIR}/chain.pem;
448
449 include ${NGINX_CONF_DIR}/options-ssl-nginx.conf;
450 ssl_dhparam ${NGINX_CONF_DIR}/ssl-dhparams.pem;
451
452 # OCSP stapling
453 ssl_stapling on;
454 ssl_stapling_verify on;
455
456 # Proxy caching
457 proxy_cache wp_cache;
458 proxy_cache_valid 200 302 1h;
459 proxy_cache_valid 404 1m;
460 proxy_cache_revalidate on;
461 proxy_cache_background_update on; 
462 proxy_cache_lock on;
463 proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;
464
465 location = /favicon.ico { 
466 log_not_found off;
467 access_log off;
468 }
469 
470 location = /robots.txt {
471 allow all;
472 log_not_found off;
473 access_log off;
474 }
475 
476 # Deny all attempts to access hidden files such as .htaccess, .htpasswd, 
477 # .DS_Store (Mac).
478 # Keep logging the requests to parse later (or to pass to firewall utilities 
479 # such as fail2ban).
480 location ~ /\.{
481 deny all;
482 }
483 
484 # Deny access to any files with a .php extension in the uploads directory.
485 # Works in sub-directory installs and also in multi-site network;
486 # Keep logging the requests to parse later (or to pass to firewall utilities 
487 # such as fail2ban).
488 location ~* / (? :uploads|files)/.*\.php\$ {
489 deny all;
490  }
491
492 # WordPress: deny wp-content, wp-includes php files
493 location ~* ^/ (? :wp-content|wp-includes)/.*\.php\$ {
494 deny all;
495 }
496 
497 # Deny public access to wp-config.php
498 location ~* wp-config.php {
499 deny all;
500 }
501 
502 # Do not log access for static assets, media
503location ~* \.(? :css(\.map)? |js(\.map)? |jpe? g|png|gif|ico|cur|heic|webp|tiff? |mp3|m4a|aac|ogg|midi? |wav|mp4|mov|webm|mpe? g|avi|ogv|flv|wmv)$ {504 access_log off;
505 }
506 
507location ~* \.(? :svgz? |ttf|ttc|otf|eot|woff2?) The ${508 add_header Access-Control-Allow-Origin "*";
509 access_log off;
510 }
511
512 location / {
513 try_files \$uri @index_php;
514 }
515 
516 location @index_php {
517 proxy_socket_keepalive on;
518 proxy_http_version 1.1;
519 proxy_set_header Connection "";
520 proxy_set_header X-Real-IP \$remote_addr;
521 proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
522 proxy_set_header X-Forwarded-Proto \$scheme;
523 proxy_set_header Host \$host;
524 
525 proxy_pass http://unit_php_upstream;
526 }
527 
528 location ~* \.php\$ {
529  proxy_socket_keepalive on;
530 proxy_http_version 1.1;
531 proxy_set_header Connection "";
532 proxy_set_header X-Real-IP \$remote_addr;
533 proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
534 proxy_set_header X-Forwarded-Proto \$scheme;
535 proxy_set_header Host \$host;
536 
537 try_files \$uri =404;
538 proxy_pass http://unit_php_upstream;
539 }
540 }
541 EOM
Copy the code

Configure Certbot to handle Let’s Encrypt certificates and automatic updates

Certbot is a free tool from the Electronic Frontier Foundation (EFF) that automatically updates TLS certificates from Let’s Encrypt. The script will do the following to configure Certbot to handle Let’s Encrypt certificates for NGINX:

  1. Closing NGINX (line 544)
  2. Download TLS parameters with the currently recommended Settings (lines 550-564)
  3. Run Certbot to retrieve TLS certificates for your site (lines 578-585)
  4. Restart NGINX to use TLS certificates (line 588)
  5. Configure Certbot to run at 3:24 am every day and check whether the TLS certificate needs to be updated; If an update is required, a new certificate is downloaded and NGINX is reloaded (lines 591-594)
543 echo "▶ Stopping NGINX in order to set up Let's Encrypt"
544 service nginx stop
545 
546 mkdir -p /var/www/certbot
547 chown www-data:www-data /var/www/certbot
548 chmod g+s /var/www/certbot
549 
550 if [ ! -f ${NGINX_CONF_DIR}/options-ssl-nginx.conf ]; then
551 echo "Download Recommended TLS Parameters"
552 curl --retry 6 -Ls -z "Tue, 14 Apr 2020 16:36:07 GMT" \
553 -o "${NGINX_CONF_DIR}/options-ssl-nginx.conf" \
554 "https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl- nginx.conf" \
555 || echo "Couldn't download latest options-ssl-nginx.conf"
556 fi
557
558 if [ ! -f ${NGINX_CONF_DIR}/ssl-dhparams.pem ]; then
559 echo "▶ Downloading recommended TLS DH parameters"
560 curl --retry 6 -Ls -z "Tue, 14 Apr 2020 16:49:18 GMT" \
561 -o "${NGINX_CONF_DIR}/ssl-dhparams.pem" \
562 "https://raw.githubusercontent.com/certbot/certbot/master/certbot/certbot/ssl-dhparams.pem" \
563  || echo "Couldn't download latest ssl-dhparams.pem"
564 fi
565
566 # If tls_certs_init.sh hasn't been run before, let's remove the self-signed certs
567 if [ ! -d "/etc/letsencrypt/accounts" ]; then
568 echo "▶ o self - signed certificates"
569  rm -rf "${CERT_DIR}"
570 fi
571
572 if [ "" = "${LETS_ENCRYPT_STAGING:-}"] | | ["0" = "${LETS_ENCRYPT_STAGING}" ]; then
573 CERTBOT_STAGING_FLAG=""
574 else
575 CERTBOT_STAGING_FLAG="--staging"
576 fi
577
578 if [ ! -f "${CERT_DIR}/fullchain.pem" ]; then
579 echo "▶ Generating certificates with Let's Encrypt"
580 certbot certonly --standalone \
581 -m "${WORDPRESS_ADMIN_EMAIL}" \
582 ${CERTBOT_STAGING_FLAG} \
583 --agree-tos --force-renewal --non-interactive \
584 -d "${TLS_HOSTNAME}"
585 fi
586 
587 echo ▶ Starting NGINX in order to use new configuration"
588 service nginx start
589
590 # Write crontab for periodic Let's Encrypt cert renewal
591 if [ "$(crontab -l | grep -m1 'certbot renew')" == "" ]; then
592 echo "▶ Adding certbot to crontab for automatic Let's Encrypt renewal"
593 (crontab -l 2>/dev/null; echo "24 3 * * * certbot renew --dry-run --nginx --post-hook 'service nginx reload'") | crontab -
594 fi
Copy the code

Customize your WordPress site

This shows you how our bash script configures NGINX open Source and NGINX Unit to host a production-ready website with TLS/SSL enabled. You may want to further customize your site to your own needs:

  • Enable Brotli for better HTTPS dynamic compression performance
  • Install ModSecurity with WordPress rules to protect your site from automated attacks
  • Set up a backup program for WordPress based on your needs
  • Use AppArmor (in Ubuntu) to protect your WordPress installation
  • Install Postfix or MSMTP to support WordPress for sending outbound emails
  • Benchmark your site to see how much traffic it can support

For better site performance, we recommend that you upgrade to NGINX Plus, our enterprise-level business support product based on NGINX Open Source. NGINX Plus users can enjoy dynamically loaded Brotli modules and NGINX ModSecurity WAF (at an additional cost). We also provide you with the NGINX Plus WAF module NGINX App Protect based on F5 industry leading security technology.


More resources

Want more timely and comprehensive access to nginx-related technical dry goods, interactive q&A, course series, and event resources? Please visit the official website of NGINX open Source community: nginx.org.cn