Skip to content

Self-Hosting Matomo on a Small VPS, Alongside Other Services

Update: I’ve since stopped using Matomo, not because it didn’t work, but because I decided I don’t need any site analytics. The remarks below are still valid, though.

I’ve recently taken up experimenting with a small Ubuntu VPS, and while still directly hosting a few Laravel-based applications, I’ve also started fiddling with Docker (Compose) as a means to keep the host OS somewhat lean.

Two apps I’m running alongside these rather plain PHP sites, are Mastodon—itself written in Ruby and requiring Redis and PostgreSQL—and Matomo—PHP again. And thus, even though I could containerize everything, my current setup looks like this:

  • NGINX, PHP, MySQL and Let’s Encrypt installed directly on the host OS
  • Mastodon, and with it Redis and PostgreSQL, inside (networked) Docker containers
  • Matomo—another PHP application—and a corresponding MySQL server, inside another set of Docker containers

That said, let’s have a look at some of the work that went into installing Matomo—a self-hosted alternative to Google Analytics.

Starting point was the NGINX example in the official GitHub repo. Since I didn’t want to run yet another NGINX instance, I commented out the corresponding lines in docker-compose.yml, made sure port 9000 would be opened up to the host OS, and used the supplied matomo.conf to add another virtual host to my existing /etc/nginx/sites-available directory instead.


    image: matomo:fpm-alpine
    restart: always
      - db
      - matomo:/var/www/html
      - ./db.env
      # Allow NGINX to easily pass PHP requests
      - 9000:9000

#  web:
#    image: nginx:alpine
#    restart: always
#    volumes:
#      - matomo:/var/www/html:ro
#      - ./matomo.conf:/etc/nginx/conf.d/default.conf:ro
#    ports:
#      - 8080:80


The virtual host server entries—I put these in a file named /etc/nginx/sites-available/matomo and then symlink to that from /etc/nginx/sites-enabled/matomo—look a little something like this. Note: I’m merely highlighting the rather important changes!

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


  # Redirect non-https requests to https
  location / { return 301 https://$host$request_uri; }

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



  # Let's Encrypt SSL certificates
  ssl_certificate /etc/letsencrypt/live/;
  ssl_certificate_key /etc/letsencrypt/live/;

  # Document root, on the host OS
  root /home/matomo/matomo;


  location ~ ^/(index|matomo|piwik|js/index|plugins/HeatmapSessionRecording/configs).php {

    # Hard-coded document root, as inside the app container it is different from the one above
    fastcgi_param SCRIPT_FILENAME /var/www/html/$fastcgi_script_name;


    # Matomo is reachable at port 9000

Next up was making sure the different directory owners were set correctly.

As should be clear from above server directive, Matomo’s docker-compose.yml is living in /home/matomo—owned by the user of the same name—and the files actually making up the web app in /home/matomo/matomo. For NGINX to be able to serve any (static) files, it needs read access to the latter, which it normally has.

For the container’s PHP process—which runs as the container’s “www-data” user, with UID 82—to be able to write to this directory, though, it has to be its owner. Similarly, /home/matomo/db needs owned by the database container’s “mysql” user, UID 999.