WordPress with NGINX SSL Reverse Proxy

WordPress is one of the widely deployed CMS platforms and it is also very popular target for hackers. In order to provide security to WordPress deployment we can configure the web server with private IP and publish the website using a Reverse Proxy.

The following guide provides details on installing WordPress on NGINX webserver with a private IP. This webserver will not be using HTTPS. We will configure another NGINX machine as a reverse proxy for the WordPress site and will perform SSL termination on the proxy.

WordPress Setup and Configuration

The latest version of WordPress 6.1.1 (as of this writing) requires PHP version 7.4 and support of PHP version 8 is still in beta. The installation will be done on Ubuntu 20.04 which has PHP 7.4 packages available. We will be use existing MariaDB server as the backend database of WordPress.

Database Configuration

Login to the database server, create database to be used for WordPress and assign privileges for the user which can use the database.

$ mysql -u root -p
MariaDB [(none)]> CREATE DATABASE websitedb;
MariaDB [(none)]> CREATE USER 'dbuser'@'%' IDENTIFIED BY 'SecurePass';
MariaDB [(none)]> GRANT ALL ON websitedb.* TO 'dbuser'@'%';

Package Installation

Install the NGINX and PHP packages on the backend webserver which will be used for WordPress deployment. NGINX does not have a PHP module. We will use PHP-FPM with NGINX for PHP support. Since the backend server is using private IP, we need to configure Proxy for the apt package manager.

Create file /etc/apt/apt.conf.d/80proxy with the proxy IP and port.

Acquire::http::Proxy "http://10.20.30.40:3128";
Acquire::https::Proxy "http://10.20.30.40:3128";

Install the required packages.

$ sudo apt install nginx nginx-core nginx-common \
php7.4 php7.4-gd php7.4-mysql php7.4-curl php-fpm

Make sure nginx and php-fpm daemons are started at boot.

$ sudo systemctl enable nginx
$ sudo systemctl enable php7.4-fpm
$ sudo systemctl start nginx
$ sudo systemctl start php7.4-fpm

NGINX PHP-FPM Configuration

In the nginx website file, configure a location block for php requests with fastcgi pass option.

server {
	listen 80 default_server;
	listen [::]:80 default_server;

	root /var/www/html;

	index index.php

	server_name _;

	location / {
		try_files $uri $uri/ /index.php?$args;
	}

	# pass PHP scripts to FastCGI server
	location ~ \.php$ {
		include snippets/fastcgi-php.conf;
	
		# With php-fpm (or other unix sockets):
		fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
	}
}

Verify nginx configuration and reload the service.

$ sudo nginx -t
$ sudo nginx -s reload

WordPress Setup

Set the proxy environment variables in your command shell and download the latest WordPress package.

$ export http_proxy="http://10.20.30.40:3128"
$ export https_proxy="http://10.20.30.40:3128"
$ wget https://wordpress.org/latest.tar.gz

Extract the wordpress installation and move it to the web server root directory.

$ tar zxvf latest.tar.gz 
$ sudo mv wordpress/* /var/www/html/

Configure proper file ownership of wordpress content with the user the NGINX server is running as.

$ sudo chown -R www-data:root /var/www/html/

In the web server root copy the wp-config-sample.php as wp-config.php

$ cd /var/www/html/
$ sudo cp wp-config-sample.php as wp-config.php

Edit the wp-config.php file and configure the database parameters.

. . . .
. . . .

// ** Database settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define( 'DB_NAME', 'websitedb' );

/** Database username */
define( 'DB_USER', 'dbuser' );

/** Database password */
define( 'DB_PASSWORD', 'StrongPass' );

/** Database hostname */
define( 'DB_HOST', dbserver.nayatel.com' );

. . . .
. . . .

Generate unique keys and salts which will be use for wordpress logins and password.

$ curl https://api.wordpress.org/secret-key/1.1/salt/ WordPress.org

define('AUTH_KEY',         'CG,Bhp>!e`h)kXf7>QN!L`0RZPuPA7|Rx~UcrRNJJyw');
define('SECURE_AUTH_KEY',  '$<;U,a[Y$f?vCWDit2H2G]!2<NL5C.|Dp4X<N>7UCsQ');
define('LOGGED_IN_KEY',    'sx#4lXe8cKy`B.2D$<l{TMEnk:Pc>L^;uY:rrxsE%E&');
define('NONCE_KEY',        '(2qlQrc#YI97+wV%in(E5~LG/XS1u9 cMn-D{3IhcH|');
define('AUTH_SALT',        '[,-@WY(6pK,||~(AGc|Og0y]sZ;``C,Jp_gRcGP]#Xq');
define('SECURE_AUTH_SALT', 'fb]V!h o/nfi5N|lW/j|Q9FnkZh-,&%C t0roiRh;=x');
define('LOGGED_IN_SALT',   'Z:,TvEh(].wX!j~~y:}|3r-Ui!W]}N{ |G6./2{4g--');
define('NONCE_SALT',       '6h|Vq~dhW5+Bm++?@TJSr!!r=7;(hh`A?~,:JZKB$.+');

Copy these in the wp-config.php file.

. . . .
. . . .

/**#@+
 * Authentication unique keys and salts.
 *
 * Change these to different unique phrases! You can generate these using
 * the {@link https://api.wordpress.org/secret-key/1.1/salt/ WordPress.org secret-key service}.
 *
 * You can change these at any point in time to invalidate all existing cookies.
 * This will force all users to have to log in again.
 *
 * @since 2.6.0
 */
define('AUTH_KEY',         'CG,Bhp>!e`h)kXf7>QN!L`0RZPuPA7|Rx~UcrRNJJyw');
define('SECURE_AUTH_KEY',  '$<;U,a[Y$f?vCWDit2H2G]!2<NL5C.|Dp4X<N>7UCsQ');
define('LOGGED_IN_KEY',    'sx#4lXe8cKy`B.2D$<l{TMEnk:Pc>L^;uY:rrxsE%E&');
define('NONCE_KEY',        '(2qlQrc#YI97+wV%in(E5~LG/XS1u9 cMn-D{3IhcH|');
define('AUTH_SALT',        '[,-@WY(6pK,||~(AGc|Og0y]sZ;``C,Jp_gRcGP]#Xq');
define('SECURE_AUTH_SALT', 'fb]V!h o/nfi5N|lW/j|Q9FnkZh-,&%C t0roiRh;=x');
define('LOGGED_IN_SALT',   'Z:,TvEh(].wX!j~~y:}|3r-Ui!W]}N{ |G6./2{4g--');
define('NONCE_SALT',       '6h|Vq~dhW5+Bm++?@TJSr!!r=7;(hh`A?~,:JZKB$.+');

. . . .
. . . .

We need to configure proxy settings for the wordpress in wp-config.php so that it can do updates of itself and download and update plugins. Also we need to configure proper HTTP Header variables so that the website is accessible via frontend SSL reverse proxy. Make sure these settings are done between the lines where it says “Add any custom values” and “Thats all, stop editing”.

. . . .
. . . .

/* Add any custom values between this line and the "stop editing" line. */
define('WP_PROXY_HOST', '10.20.30.40');
define('WP_PROXY_PORT', '3128');
define('WP_PROXY_BYPASS_HOSTS', '*.nayatel.com');

if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') {
	$_SERVER['HTTPS'] = 'on';
        $_SERVER['SERVER_PORT'] = 443;
        $_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
}

if (!empty($_SERVER['HTTP_X_FORWARDED_HOST'])) {
	$_SERVER['HTTP_HOST'] = $_SERVER['HTTP_X_FORWARDED_HOST'];
}


/* That's all, stop editing! Happy publishing. */

. . . .
. . . .

Change ownership and permission of the wp-config file so that only web server should be able to read its contents.

$ sudo chown www-data:root wp-config.php
$ sudo chmod 640 wp-config.php

NGINX Reverse Proxy Configuration

Assuming the NGINX reverse proxy is already deployed, we need to add the virtual host configuration in file /etc/nginx/conf.d/website.nayatel.com.conf for the wordpress website. We also need to restrict access to wordpress logins and admin pages to specific IPs.

Create following snippet which will be included into the virtual host configuration. This snippet defines the proxy headers to be added while proxying request to backend webserver.

# /etc/nginx/snippets/website-proxy.conf
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
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_headers_hash_max_size 512;
proxy_headers_hash_bucket_size 128;

Include the snippet into the virtual host configuration for the wordpress website. The configuration will restrict access to wordpress logins to specific IP subnet. The snippet makes the configuration cleaner otherwise we would need to repeat the proxy configurations for both locations.

server {
	listen 80;
	listen [::]:80;
	server_name website.nayatel.com;
	# Redirect HTTP->HTTPS
	return 301 https://$server_name$request_uri;
}

server {
        listen 443 ssl http2;
	listen [::]:443 ssl http2;	
        server_name  website.nayatel.com;

        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_certificate /etc/nginx/ssl/website-cert.pem;
        ssl_certificate_key /etc/nginx/ssl/website-cert.key;

        location / {
		location ~* (wp-login)\.php$ {
			allow 10.20.100.0/27;
                	deny all;
			include snippets/kbase-proxy.conf;
			proxy_pass http://172.16.16.20:80;
        	}
		include snippets/kbase-proxy.conf;
        	proxy_pass http://172.16.16.20:80;
    }
}

Completing WordPress Installation

Configure the DNS A/AAAA records for the website to point to the Reverse Proxy IP. We now need to run the WordPress install script  to create the database tables and complete the wordpress installation.

Visit the webiste http://website.nayatel.com/wp-admin/install.php in your browser and complete the required steps.

Leave a Reply