Tyler Smith

Self-hosting analytics with Plausible & NGINX on Ubuntu Server

Self-hosting is something that I find appealing. I want to have ownership of my data and services. Unfortunately, self-hosting is often impractical and expensive, but that shouldn't stop me from experimenting. In that spirit, I've decided to self-host this blog's analytics with Plausible. You can view this site's public Plausible analytics dashboard here.

Plausible is a privacy-focused Google Analytics alternative. It's lightweight and straight-to-the-point. While it seemingly only has 1/10th of the features of Google Analytics, they're the Analytics features that I use most often.

This post covers more-or-less how I got everything set up. I spun up a new $5/mo Digitalocean Droplet running Ubuntu Server (x86), but any VPS should be pretty similar. I added my ssh keys during the set up process, so I was able to log in immediately.

Creating a user account

You typically don't want to manage your server as the root user, so we'll start by creating a user account.

adduser tyler

Next, add your new user to the sudo group so it can run tasks as root.

usermod -aG sudo tyler

Next, copy the ssh keys from the root user to the new user so we can log in via ssh. We can use rsync to copy and change the ownership of the new files with a single command (you can see the Digitalocean post I got this trick from here). The -a flag is for archive mode, which you can read about from the rsync help screen.

rsync -a --chown tyler:tyler ~/.ssh /home/tyler

Next, open a new terminal and test your users login.

ssh [email protected]

If this worked, close the root ssh session: we're going to disable root login. With your non-root user, open the ssh daemon configuration with nano.

sudo nano /etc/ssh/sshd_config

Find the line with PermitRootLogin and set the value to no.

PermitRootLogin no

Save the file, then restart the ssh daemon so these changes take effect.

sudo systemctl restart ssh

Installing Docker & Docker Compose

I'm going to copy directly from their docs, so please feel free to reference the Docker Installation Guide and Docker Compose Installation Guide for the most up-to-date installation instructions.

First, remove any old versions of Docker:

sudo apt-get remove docker docker-engine docker.io containerd runc

Set up the Docker repository:

sudo apt-get update

sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg \

Add Docker’s official GPG key:

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

Add the Docker repository:

 echo \
  "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

Install Docker engine:

sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io

Copy Docker Compose onto your machine:

sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

Make Docker Compose executable:

sudo chmod +x /usr/local/bin/docker-compose

Add your user to the docker group so you don't have to run Docker as root.

sudo usermod -aG docker tyler

Run the following command for the group change to take effect without having to log out and log back in:

exec su -l $USER

Installing Plausible

Like Docker, I'm copying from the Plausible Self-Hosting Guide. Please reference the official guide for the most up-to-date installation instructions.

To keep things simple, we're going to install everything in our user's home directory. From your home directory, run the following:

git clone https://github.com/plausible/hosting
cd hosting

Next, we'll set up the config inside plausible-conf.env.

The ADMIN_USER_EMAIL, ADMIN_USER_NAME and ADMIN_USER_PWD fields will be used to create the Plausible user the first time you run the containers. After you've started the containers for the first time, you need to set up SMPT to change the password, so choose a strong password while setting this up.

Run nano plausible-conf.env to set these fields.

[email protected]

Next, set the BASE_URL to the URL where you'd like to access the dashboard.


Finally, you'll need to set a secret key to secure the app. In another terminal window, run the following command and copy its output to your clipboard:

openssl rand -base64 64

Put the command's output in your configuration file as the value for SECRET_KEY_BASE. If the output of the command was broken into multiple lines, put it all on a single line.


Start Docker Compose in detached mode:

docker-compose up -d

Plausible is now running, but we still have some extra steps before we can use it.

Set an A record on your domain's DNS pointing to the server

How you'll go about this will vary depending on your registrar or where your DNS is hosted, but make sure your domain is pointing to the server's IP.

Installing nginx

Run the following:

sudo apt update
sudo apt install nginx

Next we'll create an nginx configuration file for our site using nano.

sudo nano /etc/nginx/sites-available/your-site.com

We'll use the Plausible nginx example as a template:

server {
	# replace example.com with your domain name
	server_name your-site.com;

	listen 80;
	listen [::]:80;

	location / {
		proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

Next, link the site config to the sites-enabled folder.

sudo ln /etc/nginx/sites-available/your-site.com /etc/nginx/sites-enabled

Confirm that the configuration syntax is correct:

sudo nginx -t

Restart nginx:

sudo systemctl restart nginx

SSL with Certbot

Certbot's official docs recommend installing Certbot with snapd, but nginx recommends installing with apt. We'll follow nginx's Certbot installation instructions.

sudo apt-get update
sudo apt-get install certbot
sudo apt-get install python3-certbot-nginx

Create a certificate for your domain. The -d flags are for domains. In the example below, we're creating a certificate for both the www and non-www versions of the domains. This is a good practice, but it isn't required.

Make sure that your DNS has propagated before running this command:

sudo certbot --nginx -d your-site.com -d www.your-site.com

Setting up the firewall

Next, we'll protect the sever with ufw, short for Uncomplicated Firewall.

You can see your available apps with the following command:

sudo ufw app list

You should see the following returned:

Available applications:
  Nginx Full
  Nginx HTTP
  Nginx HTTPS

Add nginx and OpenSSH

sudo ufw allow OpenSSH
sudo ufw allow "Nginx Full"

Next, enable the firewall. Be sure that you've allowed OpenSSH before running this command, or you may be unable to access this server in the future.

sudo ufw enable

Next, run sudo ufw status. You should see the following returned:

Status: active

To                         Action      From
--                         ------      ----
OpenSSH                    ALLOW       Anywhere
Nginx Full                 ALLOW       Anywhere
OpenSSH (v6)               ALLOW       Anywhere (v6)
Nginx Full (v6)            ALLOW       Anywhere (v6)

A note about Docker and firewall security

It's worth mentioning that any ports exposed by Docker can bypass the UFW firewall. In the case of Plausible, the only port that's exposed by Docker is for the Plausible web server, which means you can access it from the browser by using your server's IP address plus :8000 (example: This isn't the end of the world: you're not exposing any of the data stored in the database. It just means that if anyone were to visit the web server directly, their traffic would be unencrypted. Even still, it's worth knowing that Docker bypasses the system firewall.

If you leave this as it is, everything will probably be fine and you can move onto the next step. However, if you want to lock port 8000 down, you have a few options.

One option is using a network firewall. DigitalOcean and Linode both have network firewalls available in their dashboards, and they can be configured via the dashboard user interface. On DigitalOcean, I set up the following for my firewall's Inbound Rules:

Type Protocol Port Range Sources
SSH TCP 22 All IPV4 / All IPV6
HTTP TCP 80 All IPV4 / All IPV6
HTTPS TCP 443 All IPV4 / All IPV6

Using a network firewall is the most surefire method of locking down exposed Docker ports. However, Reddit user ameer3141 suggested binding the port to localhost, which would prevent external devices from accessing it even if there was no firewall.

You can do this by opening docker-compose.yml using nano docker-compose.yml, changing the line that says - 8000:8000 to -, then saving the file. After that, run docker-compose down then docker-compose up -d. I haven't read about this method in depth, but it seems to work from my testing.

Logging into Plausible

Visit the URL that you set up for Plausible, then log in with the user credentials you created in plausible-conf.env.

When you log in, it will prompt you to enter a verification code that was emailed to you. We did not set up SMTP for email (though the docs has email configuration instructions you can check out).

We can side-step the email verification by running the following command:

docker exec hosting_plausible_db_1 psql -U postgres -d plausible_db -c "UPDATE users SET email_verified = true;"

Plausible is now fully set up.

Plausible First impressions

I like Plausible. I don't think I'm ready to leave Google Analytics behind for all of my websites just yet though.

Plausible shows you visitors by country. The United States is a pretty big country, and I'd like to know how many of my visitors are in Bakersfield, California (me checking the site) vs how many are elsewhere.

Plausible doesn't seem to offer that level of granularity. Truthfully, you can't offer that level of granularity and be privacy-focused, so it's a trade off.

With all of that said, I'm going to use Plausible on Tinker Log for the foreseeable future. If you haven't checked it out yet, take a look at this site's public Plausible analytics dashboard!