Skip to main content

How to Self Host Plausible Analytics

·7 mins
How to Self Host Plausible Analytics

I was in the process of setting up a few projects recently and wanted to try out using Plausible analytics rather the standard Google Analytics and I got a bit stuck configuring https access so thought I would document the process here.

There’s a great get started guide that Plausible provide by they kinda skim over the setup of a reverse proxy and SSL certificate so you can access the app over https so I’m hoping to fill in the blanks here.

Video available on YouTube

Things you’ll need #

You’ll need a server and a domain name pointed at said server (I did a video covering this a while back) and also git and docker available.

Login to server and get the Plausible code #

So the first thing I did was to actually get the Plausible code onto the server and, straight out of there installation guide, this is just a case of cloning the repository.

ssh user@domain
cd ~
git clone https://github.com/plausible/hosting plausible

I put this in the user’s home directory so I can easily find the app code later on.

Next, the installation guide asked me to update a bit of config before spinning up the Docker containers.

One bit of the information is a secret key which they also provide a handy command for.

openssl rand -base64 64 | tr -d '\n' ; echo

I then set these bits of info in the plausible-conf.env file.

nano plausible-conf.env
BASE_URL=https://<your-domain>
SECRET_KEY_BASE=<from-above>

I set the BASE_URL to the https version of the domain I’m going to be hosting the analytics on and I discovered later on that this can’t be a path e.g. https://<your-domain>/stats or something as the login pages etc. don’t get updated.

Spin up the Docker containers and test it out #

As Plausible is setup in containers the next step is to get them all up and running and as there is a docker-compose file, this can be done with one command.

sudo docker-compose up -d

The default config in the docker-compose file is to expose Plausible globally on port 8000.

So as my firewall allows it, I was able to see the Plausible login page at http://domain.com:8000/

I didn’t setup any accounts yet though, first I wanted to setup the reverse proxy with nginx and secure the login with an SSL cert.

Create a basic nginx config #

So the next thing I wanted to do was to ‘hide’ the exposed port for Plausible and put this behind nginx and then ultimately set up the secure connection.

This is the bit that the installation instructions fall a bit short on which, to be fair, it’s not really their job to tell you how to configure proxies and servers etc!

Anyway, I decided to get nginx up and running in a separate Docker container but first, I created a simple nginx config to handle traffic on port 80 and pass requests to Plausible.

server {
  listen 80;
  server_name <your-domain-name>;
  resolver 127.0.0.11;

  location ~ /.well-known/acme-challenge/ {
    root /var/www/certbot;
  }
  
  location  / {
    proxy_pass http://plausible:8000;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  }
}

So adding the proxy_pass will ensure any requests are passed to Plausible and I also setup a separate location for the certbot certificates which we’ll sort out in a moment.

It’s important to set the X-Forwarded-For header to ensure the right IP address of a visitor is passed to Plausible for the Geolocation lookups.

If you add the Docker DNS resolver you can simply reference the Plausible app container by name (http://plausible:8000) which can be updated in the docker-compose file.

## Updating docker-compose with nginx container

I did play around with using a separate Docker image and docker-compose for the nginx stuff but then I thought, they’re all linked together so might as well use the existing docker-compose file that came with the Plausible code.

So I added an nginx image that references the config file created above and networked the containers together.

The docker-compose file then looked a bit like this:

version: "3.3"
services:
  # Adding the nginx image here
  nginx:
    image: nginx:alpine
    container_name: nginx
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
      - ./certbot/www:/var/www/certbot
      - ./certbot/conf:/etc/letsencrypt
    networks:
      - plausible

  mail:
    image: bytemark/smtp
    restart: always
    networks:
      - plausible # Network containers

  plausible_db:
    # supported versions are 12, 13, and 14
    image: postgres:14-alpine
    restart: always
    volumes:
      - db-data:/var/lib/postgresql/data
    environment:
      - POSTGRES_PASSWORD=postgres
    networks:
      - plausible # Network containers

  plausible_events_db:
    image: clickhouse/clickhouse-server:22.6-alpine
    restart: always
    volumes:
      - event-data:/var/lib/clickhouse
      - ./clickhouse/clickhouse-config.xml:/etc/clickhouse-server/config.d/logging.xml:ro
      - ./clickhouse/clickhouse-user-config.xml:/etc/clickhouse-server/users.d/logging.xml:ro
    ulimits:
      nofile:
        soft: 262144
        hard: 262144
    networks:
      - plausible # Network containers

  plausible:
    image: plausible/analytics:latest
    container_name: plausible # Make sure this container has a name and matches what you have in nginx.conf
    restart: always
    command: sh -c "sleep 10 && /entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh run"
    depends_on:
      - plausible_db
      - plausible_events_db
      - mail
    ports:
      - 127.0.0.1:8000:8000 # Adding 127.0.0.1 will stop Plausible being exposed globally
    env_file:
      - plausible-conf.env
    networks:
      - plausible # Network containers

volumes:
  db-data:
    driver: local
  event-data:
    driver: local
  geoip:
    driver: local

# Add the network 
networks:
  plausible:
    driver: bridge

In the notes in the snippet above you’ll see I added a container_name to the Plausible container to ensure the DNS is resolved for the nginx.conf file and also set the exposed port to be restricted to the localhost rather than making it available publically on the domain at port 8000.

Let’s recreate all the containers.

sudo docker-compose up -d --force-recreate

All being well, you should see the Plausible login screen by simply browsing to your domain and of course the port 8000 route should be shut off.

But of course, at this point I was still working with an unsecure connection so I just needed to add a Let’s Encrypt certificate.

Adding an SSL certificate #

To add an SSL certificate I used the official certbot image and you need to configure some volumes to point to a folder in your nginx setup.

  certbot:
     image: certbot/certbot
     container_name: certbot
     volumes:
       - ./certbot/conf:/etc/letsencrypt
       - ./certbot/www:/var/www/certbot
     command: certonly --webroot -w /var/www/certbot --force-renewal --email <your-email> -d <your-domain> --agree-tos

You also need to make sure the nginx container is setup to have these volumes that match what certbot is expecting.

nginx:
    image: nginx:alpine
    container_name: nginx
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
      - ./certbot/www:/var/www/certbot # Adding these two lines
      - ./certbot/conf:/etc/letsencrypt # to match the certbot volumes
    networks:
        - plausible

Then we need to recreate the nginx container to use the new volumes.

sudo docker-compose up -d --force-recreate nginx

I would then probably give it a minute or two as I found spinning up the certbot container straight away seemed to fail as the challenges couldn’t be found by Let’s Encrypt (maybe the nginx volumes weren’t quite ready).

sudo docker-compose up -d certbot

If all is good at this point we can add the secure connection in nginx.conf.

Here’s what the nginx file should look like:

server {
  listen 80;
  server_name <your-domain>;
  resolver 127.0.0.11;
  return 301 https://$host$request_uri;
}

server {
  listen 443 ssl http2;
  ssl_certificate     /etc/letsencrypt/live/<your-domain>/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/<your-domain>/privkey.pem;
  server_name <your-domain>

  location ~ /.well-known/acme-challenge/ {
    root /var/www/certbot;
  }

  location  / {
    proxy_pass http://plausible:8000;
  }
}

You could just remove the unsecure server block but I decided to change this to redirect any non-https requests to the secure server block.

So with that in place I just needed to recreate the nginx container to get the new config.

sudo docker-compose up -d --force-recreate nginx

Once that was up and running, I could then access the Plausible login with a secure connection!

Creating an account #

So this is more about using the software but once I created an account there’s one final bit of config I did on the server.

With an account created you can then add a site which gives you the <script> tag that you can then emebd on that site to start sending analytics data.

The last bit of config I did though once I was setup is to stop any further user registrations in Plausible otherwise anyone would be able to sign up and add their own account!

To do this we just need to update the plausible-conf.env file and add an additional configuration param.

DISABLE_REGISTRATION=invite_only

You can [https://plausible.io/docs/self-hosting-configuration](turn this off completely) but I decided to keep it invite only so I could possibly add additional users in the future.

Still stuck? Check out the video for more detail!