Self-Hosting Matomo Analytics
August 1, 2026
Matomo is a self-hosted alternative to Google Analytics. You control the data, the tracking behavior, and the retention policy. No third-party JavaScript, no data sharing, no privacy concerns for your visitors. This post covers installing Matomo on Debian with nginx and PHP-FPM as the backend, MySQL as the database, and optional MaxMind GeoIP for geographic reporting. In production, Matomo sits behind an nginx reverse proxy that handles TLS termination.
Prerequisites
Before starting, you need:
- A running MySQL or MariaDB server
- nginx installed
- PHP-FPM installed and running
- A domain name pointed at your server (e.g.,
analytics.example.com)
Installing PHP and Extensions
Matomo requires PHP with several extensions. Install PHP-FPM and the necessary modules:
apt install -y php-fpm php-mysql php-curl php-gd php-xml \
php-mbstring php-zip php-intl php-opcache php-cli
Verify PHP-FPM is running:
systemctl status php8.2-fpm
The version number varies by Debian release. Check which version was installed:
php -v
Note the version (e.g., 8.2) — you'll need it for the nginx configuration and FPM socket path.
Creating the Database
Create a dedicated database and user for Matomo:
mysql << 'EOF'
CREATE DATABASE matomo CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'matomo'@'localhost' IDENTIFIED BY 'matomo-db-password-here';
GRANT ALL PRIVILEGES ON matomo.* TO 'matomo'@'localhost';
FLUSH PRIVILEGES;
EOF
Use utf8mb4 — Matomo stores page titles and URLs that may contain multibyte characters.
Downloading and Installing Matomo
Create the installation directory and download Matomo:
mkdir -p /var/www/matomo
mkdir -p /var/matomo/data
Download and extract the latest release:
apt install -y unzip
cd /tmp
curl -L -o matomo-latest.zip https://builds.matomo.org/matomo-latest.zip
unzip -q matomo-latest.zip
cp -a matomo/* /var/www/matomo/
rm -rf matomo matomo-latest.zip
Set ownership to the web server user:
chown -R www-data:www-data /var/www/matomo
chown -R www-data:www-data /var/matomo/data
Configuring nginx
Remove the default site:
rm -f /etc/nginx/sites-enabled/default
Create the Matomo site configuration at /etc/nginx/sites-available/matomo.conf:
server {
listen 80;
listen [::]:80;
server_name analytics.example.com;
root /var/www/matomo;
index index.php;
access_log /var/log/nginx/matomo_access.log;
error_log /var/log/nginx/matomo_error.log;
client_max_body_size 64M;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
# Block access to sensitive files
location ~ /\.ht {
deny all;
}
location ~ /\.git {
deny all;
}
# Serve static files directly
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
# Main location block
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# PHP handling
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_read_timeout 300;
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;
}
# Matomo tracking endpoints
location = /matomo.php {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location = /piwik.php {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
# Deny access to internal directories
location ~ ^/(config|tmp|core|lang) {
deny all;
return 404;
}
# Deny PHP execution in plugins/themes
location ~ ^/(plugins|themes)/.*\.php$ {
deny all;
return 404;
}
# Cache tracking JavaScript
location ~ ^/(matomo|piwik)\.js$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
Replace php8.2-fpm.sock with your actual PHP version. Enable the site:
ln -s /etc/nginx/sites-available/matomo.conf /etc/nginx/sites-enabled/matomo.conf
nginx -t
systemctl reload nginx
nginx Configuration Decisions
Static file caching — JavaScript, CSS, and images are served with a 1-year cache and the immutable flag. Matomo includes version hashes in its asset URLs, so cached files are automatically invalidated on updates.
Blocking sensitive directories — the config, tmp, core, and lang directories contain PHP configuration files and internal data. Denying access prevents information disclosure.
Blocking PHP in plugins/themes — prevents execution of uploaded PHP files in directories where only static assets should exist.
fastcgi_read_timeout 300 — Matomo's archive processing can run long. The default 60-second timeout is too short for large datasets.
Running the Web Installer
If this server sits behind a reverse proxy, make sure the proxy is configured to forward traffic to this backend. Then open your browser and navigate to:
https://analytics.example.com/
The Matomo web installer walks through:
- System check (verifies PHP extensions and directory permissions)
- Database configuration (use
localhost,matomo, and the password you set) - Creating the first admin user
- Adding the first website to track
- Getting the JavaScript tracking code
Save the tracking code — you'll add it to every page you want to track.
Trusted Proxy Configuration
If Matomo runs behind a reverse proxy, it needs to trust the proxy's X-Forwarded-For header to see real visitor IPs. After the web installer completes, edit /var/www/matomo/config/config.ini.php and add:
[General]
proxy_client_headers[] = HTTP_X_FORWARDED_FOR
proxy_host_headers[] = HTTP_X_FORWARDED_HOST
trusted_hosts[] = "analytics.example.com"
Setting Up the Archive Cron Job
Matomo processes raw tracking data into reports through an "archive" process. By default this happens on each page view of the dashboard, which is slow. Set up a cron job to archive in the background:
crontab -u www-data -e
Add:
5 * * * * /usr/bin/php /var/www/matomo/console core:archive --url=https://analytics.example.com/ > /dev/null 2>&1
This runs every hour at 5 minutes past the hour. Then disable browser-triggered archiving in Matomo's settings under System > General Settings > Archiving Settings and set "Archive reports when viewed from the browser" to No.
GeoIP Configuration (Optional)
Matomo can show visitor locations on a map using MaxMind's GeoLite2 database. This requires a free MaxMind account.
Setting Up MaxMind
- Create an account at maxmind.com
- Generate a license key in your account dashboard
Install the geoipupdate tool:
apt install -y geoipupdate
Configure it at /etc/GeoIP.conf:
AccountID your-account-id
LicenseKey your-license-key
EditionIDs GeoLite2-City GeoLite2-Country GeoLite2-ASN
DatabaseDirectory /usr/share/GeoIP
Run the initial download:
mkdir -p /usr/share/GeoIP
geoipupdate
Configuring Matomo for GeoIP
In Matomo's admin panel, go to System > Geolocation and select "GeoIP 2 (Php)" as the location provider. Set the GeoIP database path to /usr/share/GeoIP/GeoLite2-City.mmdb.
Auto-Updating the GeoIP Database
MaxMind updates the database weekly. Schedule automatic updates:
crontab -e
Add:
0 3 * * 3 /usr/bin/geoipupdate
This updates every Wednesday at 3:00 AM.
Creating an Update Script
For future Matomo upgrades, create /usr/local/bin/matomo-update:
#!/bin/bash
set -e
cd /var/www/matomo
echo "Downloading latest Matomo..."
curl -L -o /tmp/matomo-latest.zip https://builds.matomo.org/matomo-latest.zip
echo "Extracting..."
unzip -q -o /tmp/matomo-latest.zip -d /tmp
echo "Updating files..."
cp -a /tmp/matomo/* /var/www/matomo/
echo "Setting permissions..."
chown -R www-data:www-data /var/www/matomo
echo "Cleaning up..."
rm -rf /tmp/matomo /tmp/matomo-latest.zip
echo "Running database migration..."
sudo -u www-data php /var/www/matomo/console core:update --yes
echo "Done!"
chmod 755 /usr/local/bin/matomo-update
Run matomo-update whenever a new version is available.
Verifying the Installation
# nginx is serving Matomo
curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1/
# PHP-FPM is processing requests
curl -s http://127.0.0.1/matomo.php | head -5
# Database is accessible
mysql -e "SHOW TABLES FROM matomo;" | head -10
Summary
After completing these steps:
- Matomo is installed at
/var/www/matomoserved by nginx and PHP-FPM - Analytics data is stored in a dedicated MySQL database
- The archive cron processes reports hourly in the background
- GeoIP provides visitor location data (optional)
- An update script simplifies future version upgrades
- All visitor data stays on your server — no third-party tracking
Add the Matomo JavaScript snippet to your sites and you'll have full analytics without sending visitor data to Google or anyone else.