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

  1. Access Grafana at https://yourdomain.com/grafana/
  2. Login with default credentials (admin/admin) and set new password
  3. Navigate to Configuration → Data Sources
  4. Add data source → Search for "Infinity"
  5. 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)
  6. 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 just us-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.

Share this post