DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Tyler Smith
Tyler Smith

Posted on • Originally published at tinkerlog.dev

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 my blog's analytics with Plausible. You can view my blog'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
Enter fullscreen mode Exit fullscreen mode

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

usermod -aG sudo tyler
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

ssh tyler@myserver.com
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

PermitRootLogin no
Enter fullscreen mode Exit fullscreen mode

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

sudo systemctl restart ssh
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Set up the Docker repository:

sudo apt-get update

sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg \
    lsb-release
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Install Docker engine:

sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Make Docker Compose executable:

sudo chmod +x /usr/local/bin/docker-compose
Enter fullscreen mode Exit fullscreen mode

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

sudo usermod -aG docker tyler
Enter fullscreen mode Exit fullscreen mode

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

exec su -l $USER
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

ADMIN_USER_EMAIL=tyler@example.com
ADMIN_USER_NAME=tyler
ADMIN_USER_PWD=kmw6W4nkuV!GqC-mczCyARYZ
Enter fullscreen mode Exit fullscreen mode

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

BASE_URL=https://your-site.com
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

SECRET_KEY_BASE=WwnVrGNWl/Q2rqL18+tPqRP3hXInVpZ7uA9jywV5nGVqrdgJaSM5zAnA59c2oeDlGtu5NalOemWc+stD6M0IPg==
Enter fullscreen mode Exit fullscreen mode

Start Docker Compose in detached mode:

docker-compose up -d
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

sudo nano /etc/nginx/sites-available/your-site.com
Enter fullscreen mode Exit fullscreen mode

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_pass http://127.0.0.1:8000;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}
Enter fullscreen mode Exit fullscreen mode

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

sudo ln /etc/nginx/sites-available/your-site.com /etc/nginx/sites-enabled
Enter fullscreen mode Exit fullscreen mode

Confirm that the configuration syntax is correct:

sudo nginx -t
Enter fullscreen mode Exit fullscreen mode

Restart nginx:

sudo systemctl restart nginx
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

You should see the following returned:

Available applications:
  Nginx Full
  Nginx HTTP
  Nginx HTTPS
  OpenSSH
Enter fullscreen mode Exit fullscreen mode

Add nginx and OpenSSH

sudo ufw allow OpenSSH
sudo ufw allow "Nginx Full"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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: 143.198.111.211:8000). 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
ICMB ICMB All IPV4 / All IPV6
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 - 127.0.0.1:8000:8000, 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;"
Enter fullscreen mode Exit fullscreen mode

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 my blog for the foreseeable future. If you haven't checked it out yet, take a look at my blog's public Plausible analytics dashboard!

Top comments (0)

An Animated Guide to Node.js Event Lop

Node.js doesn’t stop from running other operations because of Libuv, a C++ library responsible for the event loop and asynchronously handling tasks such as network requests, DNS resolution, file system operations, data encryption, etc.

What happens under the hood when Node.js works on tasks such as database queries? We will explore it by following this piece of code step by step.