After successfully integrating Tinybird analytics with my Ghost blog running on Ghost-CLI, I discovered that visualizing this data required additional setup. While Tinybird collects and stores analytics data efficiently, creating beautiful, real-time dashboards requires a visualization tool. This guide walks through connecting Grafana (self-hosted) with Tinybird to create professional analytics dashboards for your Ghost blog, complete with the troubleshooting steps that took me hours to figure out.
When you integrate Tinybird with Ghost-CLI installations, you get powerful analytics data collection but limited visualization options. Tinybird's built-in charts are basic, and while you can query data with SQL, most blog owners want visual dashboards showing trends, visitor patterns, and content performance.
Grafana, the open-source analytics and monitoring platform, provides enterprise-level visualization capabilities. This guide assumes you've already set up Tinybird analytics for Ghost (if not, that's a separate process) and now want to visualize that data professionally.
Prerequisites
On Your Ubuntu Server:
- Ghost blog installed via Ghost-CLI (not Docker)
- Tinybird analytics already integrated and collecting data
- Nginx as reverse proxy
- Node.js and npm installed
- Root or sudo access
- At least 1GB free RAM for Grafana
On Tinybird:
- Active workspace with analytics_events data source
- Pipes created for data queries (daily_stats, hourly_stats, etc.)
- Read token with access to your pipes
On Your Local Machine:
- Web browser for accessing Grafana
- SSH client for server access
Full Guide
Part 1: Install Grafana on Ubuntu Server
Connect to your server via SSH and install Grafana:
bash
# Add Grafana GPG key
wget -q -O - https://packages.grafana.com/gpg.key | sudo apt-key add -
# Add Grafana repository
echo "deb https://packages.grafana.com/oss/deb stable main" | sudo tee /etc/apt/sources.list.d/grafana.list
# Update and install
sudo apt-get update
sudo apt-get install grafana -y
# Start and enable Grafana
sudo systemctl start grafana-server
sudo systemctl enable grafana-server
# Verify it's running
sudo systemctl status grafana-server
Part 2: Configure Grafana for Subdirectory Access
Edit Grafana configuration to work under a subdirectory:
bash
sudo nano /etc/grafana/grafana.ini
Find and modify these settings:
ini
[server]
protocol = http
http_port = 3000
domain = yourdomain.com
root_url = https://yourdomain.com/grafana
serve_from_sub_path = false # Important: set to false, not true
Restart Grafana:
bash
sudo systemctl restart grafana-server
Part 3: Configure Nginx Reverse Proxy
Add Grafana location to your Nginx SSL configuration:
bash
sudo nano /etc/nginx/sites-available/yourdomain-ssl.conf
Add this location block inside your server block:
nginx
location /grafana/ {
proxy_pass http://localhost:3000/;
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;
}
Test and reload Nginx:
bash
sudo nginx -t
sudo systemctl reload nginx
Part 4: Install and Configure Infinity Data Source Plugin
The default JSON plugin has limitations with Tinybird's API format. Install the Infinity plugin instead:
bash
sudo grafana-cli plugins install yesoreyeram-infinity-datasource
sudo systemctl restart grafana-server
Part 5: Create Tinybird API Endpoints
In your Tinybird workspace, create these pipes if you haven't already:
daily_stats:
sql
SELECT
toDate(timestamp) as date,
count() as pageviews,
count(DISTINCT user_agent) as unique_visitors,
count(DISTINCT url) as unique_pages
FROM analytics_events
WHERE event = 'pageview'
AND timestamp >= now() - INTERVAL 30 DAY
GROUP BY date
ORDER BY date DESC
hourly_stats:
sql
SELECT
toStartOfHour(timestamp) as hour,
count() as pageviews
FROM analytics_events
WHERE event = 'pageview'
AND timestamp >= now() - INTERVAL 24 HOUR
GROUP BY hour
ORDER BY hour DESC
realtime_today:
sql
SELECT
count() as pageviews_today,
count(DISTINCT user_agent) as visitors_today,
count(DISTINCT url) as pages_today
FROM analytics_events
WHERE event = 'pageview'
AND toDate(timestamp) = today()
top_pages:
sql
SELECT
path,
title,
count() as views
FROM analytics_events
WHERE event = 'pageview'
AND timestamp >= now() - INTERVAL 7 DAY
GROUP BY path, title
ORDER BY views DESC
LIMIT 10
Create API endpoints for each pipe and note your API base URL (e.g., https://api.us-east.aws.tinybird.co
).
Part 6: Configure Grafana Data Source
- Access Grafana at
https://yourdomain.com/grafana/
- Login with default credentials (admin/admin) and set new password
- Navigate to Configuration → Data Sources
- Add data source → Search for "Infinity"
- Configure:
- Name:
Tinybird
- Under "URL, Headers & Params":
- Base URL: Your Tinybird API URL (e.g.,
https://api.us-east.aws.tinybird.co
) - Add Header:
- Name:
Authorization
- Value:
Bearer YOUR_TINYBIRD_READ_TOKEN
(include "Bearer " with space)
- Name:
- Base URL: Your Tinybird API URL (e.g.,
- Name:
- Save & Test
Part 7: Create Dashboard Panels
Create a new dashboard and add visualizations:
Panel 1 - Daily Pageviews:
- Data source: Tinybird (Infinity)
- URL:
/v0/pipes/daily_stats.json
- Parser: JSONata
- Root/Rows:
$.data[*]
- Columns:
- date (Type: Time)
- pageviews (Type: Number)
- unique_visitors (Type: Number)
- Format: Time Series
Panel 2 - Today's Stats:
- URL:
/v0/pipes/realtime_today.json
- Root/Rows:
$.data[0]
or$.data
- Columns:
- pageviews_today (Type: Number)
- visitors_today (Type: Number)
- Format: Stat
Panel 3 - Top Pages:
- URL:
/v0/pipes/top_pages.json
- Root/Rows:
$.data[*]
- Columns:
- path (Type: String)
- title (Type: String)
- views (Type: Number)
- Format: Table
Troubleshooting Common Issues
Redirect Loop Error: If you get "redirected too many times" error, ensure serve_from_sub_path = false
in grafana.ini, not true.
403 Forbidden from Tinybird:
- Verify your API URL includes the correct region (e.g.,
us-east.aws
not justus-east
) - Ensure Authorization header includes "Bearer " before the token
- Check token has read permissions for your pipes
No Data Showing:
- Set Root/Rows to
$.data[*]
for arrays or$.data[0]
for single objects - Verify column selectors match exact field names from Tinybird response
- Check time range selector in Grafana matches your data range
Cannot Access Grafana: If subdirectory access fails, temporarily use direct port access:
bash
sudo ufw allow 3000/tcp
# Access at http://yourdomain.com:3000
JSON Plugin Limitations: The default JSON plugin may not parse Tinybird responses correctly. Always use the Infinity plugin for better compatibility.
Conclusion
Connecting Grafana with Tinybird transforms raw analytics data into actionable insights through professional visualizations. While the initial setup requires configuring multiple components—Grafana installation, Nginx proxy, plugin selection, and data source configuration—the result is a powerful, self-hosted analytics dashboard that respects user privacy while providing real-time insights into your Ghost blog's performance. The Infinity plugin proves essential for proper Tinybird integration, handling the API's JSON structure more effectively than Grafana's default options. With this setup, you maintain complete control over your analytics infrastructure while enjoying enterprise-level visualization capabilities.
Member discussion