Securing Network Communications with Your Own Certificate Authority

If you’d just like to dive into the code to secure network communications for the simple WordPress app you can skip ahead to Putting It All Together.

This is Part 7b in a series describing a project to create a local WordPress development environment using Docker-Compose. The series is structured as follows:

In Part 7a we created a certificate authority and signed certificates with it for use in securing network communications with our WordPress app. Since you installed the root certificate of this certificate authority on your PC all certificates signed by it will be trusted on your PC. You will need to have completed the setup in Part 7a to continue with Part 7b, so if you haven’t done so, do it now. You could also continue on with the tutorial series using the self-signed certificate you made in Part 6. In that case you would need to modify the nginx configuration files going forward appropriately.

In this part, I’ll cover the modifications needed to our nginx configuration file to secure communications with our WordPress app with the certificates we created in Part 7a. I’ll also discuss accessing our app’s services with the app specific domain names we assigned to those certificates. This fulfills another goal for the development environment laid out in Part 1.

NGINX Configuration File Changes

In Part 6 our nginx configuration file had two server blocks, one listening on port 80, the default http protocol port, and the other on port 443, the default https protocol port. The port 80 server passed Adminer related requests to the Adminer service and redirected all other requests to port 443 which passed the requests to our WordPress service. In this part we’ll set up individual server blocks listening on ports 80 and 443 specific to each of services. We’ll use the server_name directive to name each server block for the wordpress and adminer domains we created certificates for in Part 7a. These server blocks will then process requests specific to their assigned domains.

I’ll walk through editing our nginx configuration file from Part 6. If you’d rather just have the final file you can go to it in Putting it All Together.

First, we’ll modify the port 80 server block. We’ll need two nearly identical port 80 server blocks, one for our wordpress and adminer domains. Replace the contents of the existing port 80 server block below the listen directives with the following directives.

  server_name wordpress;

  return 301 https://wordpress$request_uri;

These lines simply name the server block for our wordpress domain and redirect all request to this domain on port 80 to the wordpress domain over https. The resulting server block will be:

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

  server_name wordpress;

  return 301 https://wordpress$request_uri;
}

We can create our port 80 server block for our adminer domain by simply copying the wordpress block and changing the server name and redirect to adminer as follows.

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

  server_name adminer;

  return 301 https://adminer$request_uri;
}

Modifying the port 443 server block is just as easy. Add the wordpress server_name directive to the exiting port 443 server and change the SSL certificate and key file names to wordpress as shown below.

  server_name wordpress;

  ssl_certificate /etc/nginx/certs/wordpress.crt;
  ssl_certificate_key /etc/nginx/certs/wordpress.key;

The resulting WordPress server block is as follows.

server {
  listen 443 ssl http2;
  listen [::]:443 ssl http2;

  server_name wordpress;

  ssl_certificate /etc/nginx/certs/wordpress.crt;
  ssl_certificate_key /etc/nginx/certs/wordpress.key;

  proxy_set_header Host $host;
  proxy_set_header X-Forwarded-Proto https;

  location / {
    proxy_pass http://wp;
  }
}

We can create our port 443 server block for the adminer domain by simply copying the wordpress block and changing the server name, certificate/key filenames and proxy pass to adminer. We also need to add the Adminer service port, 8080, to the proxy pass directive.

server {
  listen 443 ssl http2;
  listen [::]:443 ssl http2;

  server_name adminer;

  ssl_certificate /etc/nginx/certs/adminer.crt;
  ssl_certificate_key /etc/nginx/certs/adminer.key;

  proxy_set_header Host $host;
  proxy_set_header X-Forwarded-Proto https;

  location / {
    proxy_pass http://adminer:8080;
  }
}

Putting it All Together

Start with a copy of the project folder from Part 6. I’ve labeled my folder part-7. Next, create the Certificate Authority and signed certificates in the /part-7/secrets/certs folder as detailed in Part 7a – Creating Your Own Certificate Authority. Make sure you also install the Certificate Authority certificate, rootCA.crt, on your PC as described in that tutorial. The other contents of our /part-7/secrets folder are unchanged.

Our Compose file is the same as the one used in Part 6:

docker-compose.yml file for https communications

version: '3.7'

services:
  db:
    image: mariadb
    secrets:
      - mysql_root_password
      - mysql_database
      - mysql_user
      - mysql_password
    environment:
      MYSQL_ROOT_PASSWORD_FILE: /run/secrets/mysql_root_password
      MYSQL_DATABASE_FILE: /run/secrets/mysql_database
      MYSQL_USER_FILE: /run/secrets/mysql_user
      MYSQL_PASSWORD_FILE: /run/secrets/mysql_password
    volumes:
      - db:/var/lib/mysql

  wp:
    image: wordpress
    secrets:
      - mysql_database
      - mysql_user
      - mysql_password
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_NAME_FILE: /run/secrets/mysql_database
      WORDPRESS_DB_USER_FILE: /run/secrets/mysql_user
      WORDPRESS_DB_PASSWORD_FILE: /run/secrets/mysql_password
    volumes:
      - wp:/var/www/html

  adminer:
    image: adminer

  nginx:
    image: nginx
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx:/etc/nginx/conf.d
      - ./secrets/certs:/etc/nginx/certs

volumes:
  db:
  wp:      

secrets:
  mysql_root_password:
    file: ./secrets/mysql_root_password
  mysql_database:
    file: ./secrets/mysql_database
  mysql_user:
    file: ./secrets/mysql_user
  mysql_password:
    file: ./secrets/mysql_password

Our final nginx configuration file is as follows:

Updated nginx/nginx.conf file for https communications

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

  server_name wordpress;

  return 301 https://wordpress$request_uri;
}

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

  server_name adminer;

  return 301 https://adminer$request_uri;
}

server {
  listen 443 ssl http2;
  listen [::]:443 ssl http2;

  server_name wordpress;

  ssl_certificate /etc/nginx/certs/wordpress.crt;
  ssl_certificate_key /etc/nginx/certs/wordpress.key;

  proxy_set_header Host $host;
  proxy_set_header X-Forwarded-Proto https;

  location / {
    proxy_pass http://wp;
  }
}

server {
  listen 443 ssl http2;
  listen [::]:443 ssl http2;

  server_name adminer;

  ssl_certificate /etc/nginx/certs/adminer.crt;
  ssl_certificate_key /etc/nginx/certs/adminer.key;

  proxy_set_header Host $host;
  proxy_set_header X-Forwarded-Proto https;

  location / {
    proxy_pass http://adminer:8080;
  }
}

You can now start up your WordPress app with docker-compose up -d.

While you’re waiting for the database to initialize, we have one more thing to do. Up to now we’ve been accessing our app services via our server’s IP address or hostname, but one of the goals of this series was to access these via a domain name. To do this for an external site, we would simply purchase a domain name and point it to the IP address provided by our hosting service. To accomplish this in our local environment we need to edit our PC’s hosts file. I’ll be showing you how to do this for Windows 10. It’s very similar for other operating systems. You can Google edit hosts file for your operating system for the specific steps.

Setting Up Your Domain Names in Your Hosts File

Your PC’s hosts file allows you to override your DNS service and direct a given domain name to an IP address that you specify. By editing our hosts file, we can direct our wordpress and adminer domain names to our server’s IP address. You can even set the domain name of your live site, say example.com, to the IP address of your development server. Then when you browse to example.com from that PC you’d be taken to your development site rather than your live site. Of course, this has the effect of cutting off your access to your live site from that PC until you edit your hosts file again. This demonstrates some of the power and limitations of editing this file.

In Windows 10 an easy way to edit your hosts file is to press the Windows key, start typing Notepad, then selecting the Run as administrator item for the Notepad app. You need to run Notepad in administrator mode or you won’t be able to save any changes as the hosts file is in a protected directory. When you have Notepad running in administrator mode, open your hosts file at the location below.

c:\Windows\System32\Drivers\etc\hosts

Simply select File/Open and paste the line above in the File Name box (or use the shortcut ctr-O). We’ll now add two lines to our hosts file. The line consists of our server’s IP address followed by a space and our desired domain name, wordpress or adminer, in the case of our project. You can find out your Ubuntu server’s IP address with the hostname command.

hostname -I

For example, if your server’s IP address was 192.168.1.1 you would add the following two lines to the bottom of your hosts file.

192.168.1.1 wordpress
192.168.1.1 adminer

Your hosts file will then look something like (with your local server’s IP address in place of 192.168.1.1).

Edit hosts file to add the wordpress and adminer domain names

You can now save and close your hosts file.

Once you’ve edited your hosts file and your database is initialized you can visit the WordPress service in your browser with http://wordpress and your Adminer service with http://adminer (you can also access them directly with https://). Nginx will redirect your http request to a https connection. If you’ve installed your Certificate Authority certificate correctly and properly created the certificate/key pairs then you should be taken directly to your services. If you get a warning that your connection isn’t secure, try clearing your browsers cache or restarting your browser.

Wrapping Up

With the tasks we’ve completed so far in this series, we’ve come pretty far in creating a fairly complete WordPress testing environment even with the barebones configurations we’ve used so far. In Part 8 – Hosting multiple WordPress sites on a single database server, we’ll explore how to add additional WordPress services, all utilizing our existing MariaDB database. That will take us a step further in our goal to create a WordPress development environment. It will also be useful for anyone wanting to host several separate WordPress sites within a simple Docker Compose app.