This is the second section in the 4 part series of migrating my server
- Install and configure the host machine
- Install and configure a database and webserver - we are here
- Install and configure a mailserver
- Install and configure vaultwarden
- Tie everything back to 1. for backups, misc, etc
Dockerized services
I will configure a database and a webserver(mainly wordpress container) to listen internally only and not exposed to the outside network. I will also configure a nginx-proxy and letsencrypt to provide the access from the world to the wordpress site and to generate the letsencrypt certificate.
Wanted to note that I like to create a directory where I mount to all of my containers so that I can easily transfer files between containers and the host. For this purpose I've created /opt/temp
and I will add -v /opt/temp:/temp
onto all the containers so that I can mount and share files back and forth between all the containers and the host.
The database and wordpress is specific for this section but the nginx-proxy and letsencrpyt can be used for other sections as well to provide a secure network access to other services.
Database service
Lets start with the database service. I am going to install mariadb. Why mariadb instead of mysql? not sure but since I been using mariadb for a while why not?
- Create directories - I am using
/opt/mariadb
as my persistent storage for mariadb
mkdir -p /opt/mariadb
- Create my
docker-mariadb.service
[Unit] Description=mariadb docker container Requires=docker.service After=docker.service [Service] Restart=always RestartSec=90 ExecStartPre=-/usr/bin/docker kill mariadb ExecStartPre=-/usr/bin/docker rm mariadb ExecStart=/usr/bin/docker run \ --name mariadb \ --net internal \ -e MARIADB_RANDOM_ROOT_PASSWORD=yes \ -v /etc/localtime:/etc/localtime:ro \ -v /opt/temp:/temp \ -v /opt/mariadb:/var/lib/mysql \ mariadb:latest ExecStop=/usr/bin/docker stop mariadb [Install] WantedBy=multi-user.target
- Enable and start mariadb
# systemctl daemon-reload # systemctl enable docker-mariadb # systemctl start docker-mariadb
- Find the root password
$ docker logs mariadb | grep "GENERATED" 2021-12-19 00:35:26+00:00 [Note] [Entrypoint]: GENERATED ROOT PASSWORD: v(/-*lHy=Wn2bl?a.&hP&%#eg#cS*.[I
- Secure mariadb - follow all the instructions from
/usr/bin/mysql_secure_installation
and remember to keep the new password safe
$ docker exec -it mariadb /bin/bash root@b8e4dee1a09e:/# /usr/bin/mysql_secure_installation NOTE: RUNNING ALL PARTS OF THIS SCRIPT IS RECOMMENDED FOR ALL MariaDB SERVERS IN PRODUCTION USE! PLEASE READ EACH STEP CAREFULLY! In order to log into MariaDB to secure it, we'll need the current password for the root user. If you've just installed MariaDB, and haven't set the root password yet, you should just press enter here. Enter current password for root (enter for none): ...
We now have a mariadb service running thats been secured and best part there are no network ports exposed at all
wordpress service
Since I will be hosting wordpress sites I will just run the wordpress containers instead of running a dedicated webserver.
- Create directories
$ mkdir -p /opt/wp-site1 $ mkdir -p /opt/config
- Create the service file
/etc/systemd/system/docker-wp-site1.service
[Unit] Description=wp-site1 docker container Requires=docker-mariadb.service After=docker.service [Service] Restart=always RestartSec=90 ExecStartPre=-/usr/bin/docker kill wp-site1 ExecStartPre=-/usr/bin/docker rm wp-site1 ExecStart=/usr/bin/docker run \ --name wp-site1 \ --net internal \ -e VIRTUAL_HOST=www.site1.com,site1.com \ -e VIRTUAL_PORT=80 \ -e LETSENCRYPT_EMAIL=your@email.address \ -e LETSENCRYPT_HOST=www.site1.com,site1.com \ --env-file /opt/config/env.site1 \ -v /etc/localtime:/etc/localtime:ro \ -v /opt/temp:/temp \ -v /opt/wp-site1:/var/www/html \ wordpress:latest ExecStop=/usr/bin/docker stop wp-site1 [Install] WantedBy=multi-user.target
VIRTUAL*
will be used by ngnix-proxy to configure the proxy and LETSENCRYPT*
will be used by the letsencrypt to request and generate the SSL certificate for nginx-proxy to use
- Create
/opt/config/env.wp-site1
WORDPRESS_DB_HOST=mariadb # name of your database container WORDPRESS_DB_USER=siteuser # username for your database - dont use root WORDPRESS_DB_PASSWORD=sitepass # password for your database WORDPRESS_DB_NAME=wpsite1 # name of your database
- Create your database and user
$ docker exec -it mariadb /bin/bash root@52c97fb980b3:/# mysql -u root --password=xxxxxxxx MariaDB [(none)] > CREATE DATABASE wpsite1; MariaDB [(none)] > GRANT ALL PRIVILEGES ON wpsite1.* TO "siteuser"@"%" IDENTIFIED BY "sitepass"; MariaDB [(none)] > FLUSH PRIVILEGES; MariaDB [(none)] > exit
- Enable and start my wordpress site
# systemctl daemon-reload # systemctl enable docker-wp-site1 # systemctl start docker-wp-site1
We now have a wordpress site running! Too bad you can only access it from the localhost on port 8080 fix this!
letsencrypt service
Letsencrypt container will use certbot to generate valid SSL certificates for your server. My DNS is setup with a wildcard *.example.com
to my IP to make things easier but you can configure your own specific DNS.
- Create directory
$ mkdir -p /opt/certbot $ mkdir -p /opt/nginx-proxy/certs $ mkdir -p /opt/nginx-proxy/vhost $ mkdir -p /opt/nginx-proxy/html
- Create the service file
/etc/systemd/system/docker-letsencrypt.service
[Unit] Description=letsencrypt docker container Requires=docker.service After=docker.service [Service] Restart=always RestartSec=90 ExecStartPre=-/usr/bin/docker kill letsencrypt ExecStartPre=-/usr/bin/docker rm letsencrypt ExecStart=/usr/bin/docker run \ --name letsencrypt \ --net internal \ -e NGINX_PROXY_CONTAINER=nginx-proxy \ -v /etc/localtime:/etc/localtime:ro \ -v /opt/temp:/temp \ -v /opt/nginx-proxy/certs:/etc/nginx/certs:rw \ -v /opt/nginx-proxy/vhost.d:/etc/nginx/vhost.d:rw \ -v /opt/nginx-proxy/html:/usr/share/nginx/html:rw \ -v /var/run/docker.sock:/var/run/docker.sock:ro \ -v acme:/etc/acme.sh \ -e "DEFAULT_EMAIL=your@email.address" \ nginxproxy/acme-companion ExecStop=/usr/bin/docker stop letsencrypt [Install] WantedBy=multi-user.target
NGINX_PROXY_CONTAINER
needs to be the container name of your ngnix-proxy. This container will use the http-01
method to validate and have the certs signed by letsencrypt. It will watch the docker daemon to see if any containers have VIRTUAL*
envvars to create new certs and it will auto-renew certs when needed.
- Enable and start the service
# systemctl daemon-reload # systemctl enable docker-letsencrypt # systemctl start docker-letsencrypt
nginx-proxy service
nginx-proxy will provide the network connectivy from the internet to your wordpress instances.
- Create directories - most were created already from the previous step
$ mkdir -p /opt/nginx-proxy/htpasswd
- Create
/opt/nginx-proxy/proxy.conf
- this is good for most but you might need to customize
# Increase max upload size client_max_body_size 100G; # Increase timeouts send_timeout 100m; proxy_connect_timeout 600; proxy_send_timeout 600; proxy_read_timeout 30m; # Increase header buffer size client_header_buffer_size 64k; large_client_header_buffers 4 64k; # Don't disable iframes proxy_hide_header X-Frame-Options; # gzip gzip on; gzip_vary on; gzip_min_length 1000; gzip_proxied any; gzip_disable "MSIE [2-6]\.";
- Create our service file
/etc/systemd/system/docker-nginx-proxy.service
[Unit] Description=nginx-proxy docker container Requires=docker.service After=docker.service [Service] Restart=always RestartSec=90 ExecStartPre=-/usr/bin/docker kill nginx-proxy ExecStartPre=-/usr/bin/docker rm nginx-proxy ExecStart=/usr/bin/docker run \ --name nginx-proxy \ --net internal \ -p 80:80 \ -p 443:443 \ -v /etc/localtime:/etc/localtime:ro \ -v /opt/temp:/temp \ -v /opt/nginx-proxy/certs:/etc/nginx/certs:ro \ -v /opt/nginx-proxy/vhost.d:/etc/nginx/vhost.d:rw \ -v /opt/nginx-proxy/htpasswd:/etc/nginx/htpasswd:ro \ -v /var/run/docker.sock:/tmp/docker.sock:ro \ -v /opt/nginx-proxy/proxy.conf:/etc/nginx/conf.d/proxy.conf:ro \ -v /opt/nginx-proxy/html:/usr/share/nginx/html:rw \ jwilder/nginx-proxy:latest ExecStop=/usr/bin/docker stop nginx-proxy [Install] WantedBy=multi-user.target
You can see that we are binding port 80 and 443 to this container. This container will monitor your docker socket and auto create proxies based on the VIRTUAL*
settings on your docker containers.
- Enable and start the service
# systemctl daemon-reload # systemctl enable docker-nginx-proxy # systemctl start docker-nginx-proxy
Now you should be able to browse to your www.example.com and configure your wordpress site. To spin up additional sites follow the same steps and create new service and directories for your other wordpress instances.
Lets secure our wordpress site. If you can remember from the first part of this series we installed fail2ban and configured it. I will setup a custom fail2ban jail for wp-login
- Create a jail
/etc/fail2ban/jail.d/wp-login.local
- Since our container logs will also show in syslog on the host for bothnginx-proxy
andwordpress
containers we can just read from/var/log/messages
[wp-login] enabled = true port = http,https filter = wp-login logpath = /var/log/messages banaction = wp-login
- Create a filter
/etc/fail2ban/filter.d/wp-login.conf
- this was adjusted to fit the new format
[Definition] failregex = ^.* docker.*:-.*POST.*wp-login.php.*$
- Create the action
/etc/fail2ban/action.d/wp-login.conf
[Definition] actionstart = iptables -N f2b-wplogin iptables -A f2b-wplogin -j RETURN iptables -I FORWARD -p tcp -m multiport --dports 80 -j f2b-wplogin actionstop = iptables -D FORWARD -p tcp -m multiport --dports 80 -j f2b-wplogin iptables -F f2b-wplogin iptables -X f2b-wplogin actioncheck = iptables -n -L FORWARD | grep -q 'f2b-wplogin[ \t]' actionban = iptables -I f2b-wplogin 1 -s-j DROP actionunban = iptables -D f2b-wplogin -s -j DROP
- restart and reload fail2ban
# systemctl restart fail2ban # fail2ban-client reload
- Check in on the jail
# fail2ban-client status wp-login Status for the jail: wp-login |- Filter | |- Currently failed: 1 | |- Total failed: 1 | `- File list: /var/log/messages `- Actions |- Currently banned: 0 |- Total banned: 0 `- Banned IP list:
I can try to fail login on my wp-login and it does show up in the logs
in /var/log/messages
Dec 19 21:22:47 hostname docker[1521479]: x.x.x.x - - [19/Dec/2021:21:22:47 -0600] "POST /wp-login.php HTTP/1.1" 503 19461 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36"
in /var/log/fail2ban.log
2021-12-19 21:22:48,358 fail2ban.filter [1519807]: INFO [wp-login] Found x.x.x.x - 2021-12-19 21:22:47
Lastly we can see that our containers are not exposed on the network at all except for the nginx-proxy
on port 80 and 443
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES daf109afe7c7 wordpress:latest "docker-entrypoint.s…" 34 minutes ago Up 34 minutes 80/tcp wp-site1 343ca6f43e14 jwilder/nginx-proxy:latest "/app/docker-entrypo…" 6 hours ago Up 6 hours 0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp nginx-proxy 3e108dc3ae0a nginxproxy/acme-companion "/bin/bash /app/entr…" 6 hours ago Up 6 hours letsencrypt 52c97fb980b3 mariadb:latest "docker-entrypoint.s…" 3 days ago Up 3 days 3306/tcp mariadb
One Comment