2. NginX (which we’re using for both SSL termination as well as main content webserver, of course)

On my box, it’s as easy as typing:

sudo apt-get install nginx

However, if you prefer (and I do recommend it) – you can grab the latest version available publicly at At the time of the writing, it’s 1.13.1. Here’s how you install this one:

tar xvzf nginx-1.13.1.tar.gz
cd nginx-1.13.1

If you went this way you’re certain that you are running the latest version, which means your box is not super vulnerable to all sorts of exploits and remote compromise at this layer.

Note that this isn’t even close to all the hardening actions you need to take to secure your machine, and we’ll get around to that later.

Now, let’s configure Nginx to both deal with encryption (SSL certs) as well as actually serve some content:

a) Nginx: SSL termination:
  • first, obtain a free SSL cert from LetsEncrypt. I’m not going into details, read their documentation, it’s super simple to run ‘certbot certonly‘ and follow the prompts. If you are successful in installing the certbot and run it, you’ll be the proud owner of an SSL cert for your domain(s), typically found at: /etc/letsencrypt/live/<yourdomain>/; and there are a few files under there, whose paths you’ll have to fill into Nginx’s configuration below:
    • cert.pem
    • chain.pem
    • fullchain.pem
    • privkey.pem
  • in /etc/nginx/sites-enabled/ there’s a ‘default‘ file, which is a symlink to ../sites-available/default. ‘mv‘ that away in your home dir. Then create your own file in sites-available, then link that to sites-enabled, like so:
    • sudo mv /etc/nginx/sites-enabled/default ~/nginx-default-site-configurationsudo touch /etc/nginx/sites-available/<your-domain>-ssl.conf

      sudo ln -s /etc/nginx/sites-available/<your-domain>-ssl.conf /etc/nginx/sites-enabled/
  • again, break out your favourite editor and configure it, similar to this (be sure to replace <your domain> with your actual domain, like or whatever):
    • sudo vi /etc/nginx/sites-enabled/<your-domain>-ssl.conf
    • paste this bit, and modify to reflect your domain and chosen paths to the correct files:
      server {
       # listens both on IPv4 and IPv6 on 443 and enables HTTPS and HTTP/2 support.
       # HTTP/2 is available in nginx 1.9.5 and above.
       listen *:443 ssl http2;
       listen [::]:443 ssl http2;
      # indicate locations of SSL key files.
       ssl_certificate /etc/letsencrypt/live/<your-domain>/fullchain.pem;
       ssl_certificate_key /etc/letsencrypt/live/<your-domain>/privkey.pem;
       ssl_dhparam /etc/ssl/certs/dhparam.pem;
      # Enable HSTS. This forces SSL on clients that respect it, most modern browsers. The includeSubDomains flag is optional.
       add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
      # Set caches, protocols, and accepted ciphers. This config will merit an A+ SSL Labs score as of Sept 2015.
       ssl_session_cache shared:SSL:20m;
       ssl_session_timeout 10m;
       ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
       ssl_prefer_server_ciphers on;
       ssl_ciphers 'ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5';
      server_name <your-domain>.com www.<your-domain>.com;
      location / {
       # First attempt to serve request as file, then
       # as directory, then fall back to displaying a 404.
       #try_files $uri $uri/ =404;
       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 https;
       proxy_set_header X-Forwarded-Port 443;
       proxy_set_header Host $host;
       # We expect the downstream servers to redirect to the right hostname, so don't do any rewrites here.
       proxy_redirect off;
       access_log /var/log/nginx/https-<your-domain>-access.log;
       error_log /var/log/nginx/https-<your-domain>-error.log;
    • The above configuration only deals with encryption/decryption (SSL) and forwards or proxies the request to whatever software is listening on port 80 (default http port on most systems). In our case, that will be Varnish, but we’ll get to that in a bit, we’re still not done with Nginx yet.
b) Nginx: actually serve some content, over plain http/2
  • just like above, except now we need another server { … } block listening on port 8080, in its own file, under sites-available, and sym-linked under sites-enabled, like so:
    • sudo touch /etc/nginx/sites-available/<your-domain>-nonssl.conf
      sudo ln -s /etc/nginx/sites-available/<your-domain>-nonssl.conf /etc/nginx/sites-enabled/
      sudo vi /etc/nginx/sites-enabled/<your-domain>-nonssl.conf
    • and paste this, again, modifying it accordingly to suit your domain’s details and file paths and whatnot:
    • server {
       listen *:8080;
       listen [::]:8080;
      # indicate the server name
       server_name <your-domain>.com;
      root /var/www/<your-domain>.com;
      # Add index.php to the list if you are using PHP
       index index.php index.html index.htm index.nginx-debian.html;
      location / {
       # First attempt to serve request as file, then
       # as directory, then fall back to displaying a 404.
       #try_files $uri $uri/ =404;
       try_files $uri $uri/ /index.php?$args;
      # pass the PHP scripts to FastCGI server listening on
       location ~ \.php$ {
       # With php7.0-fpm:
       fastcgi_pass unix:/run/php/php7.0-fpm.sock;
       fastcgi_intercept_errors on;
       fastcgi_keep_conn on;
       location ^~ /xmlrpc.php {
       deny all;
       location ~ /\. {
       deny all;
       location /wp-json/ {
       deny all;
       # logs
       access_log /var/log/nginx/http-<your-domain>-access.log;
       error_log /var/log/nginx/http-<your-domain>-error.log;
    • Note the port 8080, this time, instead of 443 like before

Write a Comment


  1. HTTP/1.1 403 Forbidden
    Server: nginx/1.10.3 (Ubuntu)
    Date: Sat, 29 Sep 2018 17:23:22 GMT
    Content-Type: text/html; charset=utf-8
    Connection: keep-alive
    Expires: Wed, 11 Jan 1984 05:00:00 GMT
    Cache-Control: no-cache, must-revalidate, max-age=0

    i get this response

    • would you mind posting both your NGINX configuration as well as your Varnish configuration? Also, please be specific as to your OS flavor and version.

  2. 1. Your tutorial works for me. But after I logged in with admin account, access any url with “/wp-admin/*”, results a 302 (see below). Any ideas? – – [13/Dec/2018:15:48:34 +0000] “GET /home/wp-admin/post-new.php HTTP/1.1” 302 0 “” “Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36”

    2. In some tutorial, they added the following configs. Do we need them?

    define( ‘WP_HOME’, ‘https://your-site’ );
    define( ‘WP_SITEURL’, ‘https://your-site’ );
    define(‘FORCE_SSL_ADMIN’, true);
    define(‘FORCE_SSL_LOGIN’, true);