Why GoatCounter?
- Privacy-first: No cookies, no personal data tracking, no GDPR consent banners required
- Lightweight: Single Go binary with ~3.5 KB tracking script — minimal impact on page load times
- Simple dashboard: Clean, accessible interface focused on meaningful metrics over overwhelming data
- Flexible integration: JavaScript snippet, no-JS tracking pixel, backend middleware, or log file import
- Open source: EUPL-licensed, fully auditable, no vendor lock-in
A RamNode 2 GB KVM VPS at $10/month provides more than enough headroom for even high-traffic sites. RamNode's 99.99% uptime SLA and premium network ensure your analytics remain consistently available.
Prerequisites
| Requirement | Details |
|---|---|
| RamNode VPS | 2 GB KVM or higher (1 GB works for low-traffic sites) |
| Operating System | Ubuntu 24.04 LTS (clean install recommended) |
| Domain/Subdomain | e.g., stats.yourdomain.com, pointed to your VPS IP via DNS A record |
| SSH Access | Root or sudo-capable user with SSH key authentication |
| DNS Propagation | Ensure DNS A record is active before starting |
GoatCounter requires a dedicated subdomain (e.g., stats.yourdomain.com). Configure your DNS A record to point to your RamNode VPS IP address before proceeding. DNS propagation can take up to 24–48 hours.
Initial Server Setup
ssh root@YOUR_VPS_IP
apt update && apt upgrade -yCreate a Dedicated Service User
Running GoatCounter as a dedicated non-root user follows security best practices and limits the blast radius of any potential vulnerability.
useradd goatcounter --system --user-group \
--shell /sbin/nologin \
--comment "GoatCounter web analytics" \
--create-home \
--home-dir /var/lib/goatcounterConfigure Firewall
apt install ufw -y
ufw allow OpenSSH
ufw allow 80/tcp
ufw allow 443/tcp
ufw enableInstall GoatCounter
GoatCounter distributes statically compiled binaries that include everything needed — no additional runtime dependencies required.
cd /var/lib/goatcounter
wget https://github.com/arp242/goatcounter/releases/download/v2.7.0/goatcounter-v2.7.0-linux-amd64.gz
gzip -d goatcounter-v2.7.0-linux-amd64.gz
mv goatcounter-v2.7.0-linux-amd64 /usr/local/bin/goatcounter
chmod +x /usr/local/bin/goatcountergoatcounter versionInitialize the Database
GoatCounter supports both SQLite and PostgreSQL. SQLite is the simplest option and performs well for most sites. For high-traffic deployments handling millions of daily page views, consider PostgreSQL.
Option A: SQLite (Recommended)
cd /var/lib/goatcounter
sudo -u goatcounter /usr/local/bin/goatcounter db create site \
-createdb \
-user.email admin@yourdomain.com \
-vhost stats.yourdomain.comYou will be prompted to set a password for the dashboard login. This creates the SQLite database at /var/lib/goatcounter/goatcounter-data/db.sqlite3.
Option B: PostgreSQL (High-Traffic Sites)
apt install postgresql postgresql-contrib -y
systemctl enable postgresql
sudo -u postgres createuser --interactive --pwprompt goatcounter
sudo -u postgres createdb --owner goatcounter goatcounter
sudo -u goatcounter /usr/local/bin/goatcounter db create site \
-db "postgresql+host=/run/postgresql dbname=goatcounter" \
-user.email admin@yourdomain.com \
-vhost stats.yourdomain.comConfigure the Systemd Service
Create a systemd unit file so GoatCounter starts automatically on boot and restarts on failure.
[Unit]
Description=GoatCounter web analytics
After=network.target
[Service]
Type=simple
Restart=always
RestartSec=5
WorkingDirectory=/var/lib/goatcounter
ExecStart=/usr/local/bin/goatcounter serve \
-listen :8091 \
-tls none
User=goatcounter
Group=goatcounter
CapabilityBoundingSet=
PrivateTmp=true
NoNewPrivileges=true
PrivateDevices=true
DevicePolicy=closed
[Install]
WantedBy=multi-user.targetThe -tls none flag tells GoatCounter not to handle TLS directly. We'll use Nginx as a reverse proxy with Let's Encrypt certificates instead.
For PostgreSQL users, modify the ExecStart line to include your database connection:
ExecStart=/usr/local/bin/goatcounter serve \
-listen :8091 \
-tls none \
-db "postgresql+host=/run/postgresql dbname=goatcounter"systemctl daemon-reload
systemctl enable goatcounter
systemctl start goatcounter
systemctl status goatcounterConfirm the service shows "active (running)". If there are issues, check the logs:
journalctl -fu goatcounterNginx Reverse Proxy with SSL
apt install nginx certbot python3-certbot-nginx -yserver {
listen 80;
server_name stats.yourdomain.com;
location / {
proxy_pass http://127.0.0.1:8091;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}ln -s /etc/nginx/sites-available/goatcounter /etc/nginx/sites-enabled/
nginx -t
systemctl reload nginx
# Obtain SSL certificate
certbot --nginx -d stats.yourdomain.comcertbot renew --dry-runAdd Tracking to Your Website
Method 1: JavaScript Snippet (Recommended)
Add the following script tag just before the closing </body> tag. The script is approximately 3.5 KB and loads asynchronously with negligible performance impact.
<script data-goatcounter="https://stats.yourdomain.com/count"
async src="//stats.yourdomain.com/count.js"></script>Method 2: No-JavaScript Tracking Pixel
For sites that need to work without JavaScript:
<noscript>
<img src="https://stats.yourdomain.com/count?p=/page-path">
</noscript>Method 3: Server-Side Log Import
GoatCounter can parse access logs from Nginx, Apache, Caddy, CloudFront, and other servers:
goatcounter import -follow -format combined /var/log/nginx/access.logVerify the Dashboard
- Open your browser and navigate to
https://stats.yourdomain.com - Log in with the email and password from the database initialization step
- Visit your website in a separate tab to generate test traffic
- Return to the GoatCounter dashboard — page views should appear within seconds
The dashboard displays page views, referrers, browser and OS statistics, screen sizes, locations, and campaign tracking — all without collecting personally identifiable information.
Production Hardening
Alternative: Docker Deployment
GoatCounter is also available on Docker Hub for containerized deployments.
docker run -d \
--name goatcounter \
--restart unless-stopped \
-p 8091:8080 \
-v goatcounter-data:/home/goatcounter/goatcounter-data \
arp242/goatcounterversion: "3.8"
services:
goatcounter:
image: arp242/goatcounter
container_name: goatcounter
restart: unless-stopped
ports:
- "8091:8080"
volumes:
- goatcounter-data:/home/goatcounter/goatcounter-data
volumes:
goatcounter-data:After starting the container, use the same Nginx reverse proxy configuration from Step 7. The named volume ensures your database persists across container restarts.
Troubleshooting
| Issue | Solution |
|---|---|
| Dashboard login refreshes without logging in | Ensure your reverse proxy passes the X-Forwarded-Proto: https header so GoatCounter sets secure cookies correctly |
| "zdb requires SQLite 3.35.0 or newer" | Usually a file permission issue. Verify ownership: chown -R goatcounter:goatcounter /var/lib/goatcounter |
| Service fails to start on port 80/443 | Use setcap or run behind a reverse proxy on a high port (8091) as shown in this guide |
| No page views appearing | Verify the tracking script URL matches your vhost domain exactly. Check browser dev tools for count.js loading errors |
| High memory usage with SQLite | Run VACUUM periodically: sqlite3 /path/to/db.sqlite3 "VACUUM" |
| Certificate renewal fails | Ensure port 80 is open for ACME challenges. Check: journalctl -u certbot |
Quick Reference
| Command | Description |
|---|---|
| systemctl status goatcounter | Check service status |
| systemctl restart goatcounter | Restart the service |
| journalctl -fu goatcounter | View live logs |
| goatcounter db migrate all | Run pending migrations |
| goatcounter db create site | Create a new site entry |
| goatcounter import -format combined | Import access logs |
| goatcounter help serve | View all serve options |
| goatcounter version | Check installed version |
GoatCounter Deployed Successfully!
Your self-hosted, privacy-friendly analytics platform is now running. Add the tracking snippet to your websites and enjoy clean, cookie-free analytics.
