Migrating WordPress from DO to Hetzner

I’ve been running Wordpress sites on Digital Ocean for multiple years now. Recently I moved from Digital Ocean to Hetzner and took the opportunity to re-invent my setup.

This had multiple reasons:

  • I’m doing more ECommerce sites and downscaling creating sites
  • Costs for Digital Ocean were increasing, it’s ~80€ / m. for 3 droplets
  • Some sites were moved, renewed by external parties, never received further feedback from customers, …

My old architecture was very simple. Each droplet looked like this:

This setup had some disadvantages:

  • Annoying to maintain and migrate. Unused sites took up resources
  • If one WP was outdated. There were security issues that could affect all WP instances on that droplet

I did enjoy https://easyengine.io/. It handles everything from NGinx, Wordpress and SSL through the cli.

A bit about Hetzner

Hetzner is a Cloud computing company from Germany that has DC’s accross Europe and the US. It manages it’s DC’s themselves which means they have a low operating costs.

For more info about the Hetzner:

Actually Moving

I picked a a reverse proxy in combination with Docker and Portainer. So I could manage all my Docker instances through a Web UI and easily create+delete any Wordpress sites.

For every Wordpress instance, a Nginx config file is required. This redirects the domain to the running WP instance.

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name my-site.be www.my-site.be;
    client_max_body_size 500M;

    # SSL
    ssl_certificate /etc/letsencrypt/live/www.my-site.be/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/www.my-site.be/privkey.pem; # managed by Certbot

    # Redirect non-https traffic to https
    proxy_redirect off;

    location / {
        proxy_redirect off;
        proxy_pass http://localip:dockerport; #Important: Port of the docker Wordpress instance
        proxy_set_header        Host $host; #Forwards to domain, eg. 'my-site.be' in this example
        proxy_set_header        X-Real-IP $remote_addr; #Forwards the IP from the visitor
        proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header        X-Forwarded-Proto $scheme;
        proxy_set_header        X-Forwarded-Host  $host;
        proxy_set_header        X-Forwarded-Port  $server_port;
        add_header X-Location MY-SITE;
server {
    if ($host = my-site.be) {
        return 301 https://$host$request_uri;
    } # managed by Certbot

    if ($host = www.my-site.be) {
        return 301 https://$host$request_uri;
    } # managed by Certbot

    listen       80;
    listen  [::]:80;
    server_name my-site.be www.my-site.be;
    return 404; # managed by Certbot

Moving the WP data went fairly fluent with Updraft. I used the free version to download the backups and restore them on the new instance.

I’m using certbot in combination with cronjobs to renew SSL certificates.

This is how the end-result looks like:


I realized a 90% cost reduction ( 7,72 € / m) by just moving to Hetzner. The flow made it much easier to create and remove Wordpress instances.

Since this has been deployed for multiple months. Here are some things to remember:

1 - Use Fixed Docker ports for your WP instance

My first reboot of the server led to all containers using new ports. Took some moments to find the root cause and applying the fix.

2 - SSL flow could be improved

The cronjob didn’t have sufficient permissions the first time, so all sites went dark for a couple of hours 90 days later ( the SSL was valid for 90 days). The proper implementation would have been to setup a docker container which has a shared partition with the NGinx instance. That Docker instance should solely be responsible for renewing the certificates.

If you want to improve this flow further. Consider using Apache APISIX, an API on top of NGinx and this could avoid messing with config files in NGinx through an API.

All in all, this has been worth the effort.

See also