How to Install Grafana on Ubuntu 24.04 VPS — Complete Setup Guide
Grafana is the de facto standard open-source dashboard for time-series data, logs, traces, and alerts. This guide walks you through a production-grade install on an Ubuntu 24.04 LTS VPS, from the first apt update to OAuth single sign-on, Nginx TLS, and connected datasources for Prometheus, Loki, and InfluxDB. Every step has been tested on the CloudCore Starter plan and scales cleanly up to larger tiers.
Looking for an affordable monitoring VPS? The CloudCore Starter plan at EUR 7.99/month runs Grafana plus a single-node Prometheus with ease. Upgrade paths are frictionless.
Table of Contents
What is Grafana?
Grafana is an open-source analytics and visualization platform that lets you query, graph, alert on, and explore metrics, logs, and traces from dozens of backends. Instead of every data store shipping its own UI, Grafana becomes the single pane of glass — you connect it to a time-series database (like Prometheus, InfluxDB, VictoriaMetrics), a log aggregator (like Loki, Elasticsearch), or a traditional RDBMS (MySQL, PostgreSQL), and build dashboards that combine all of them in one view.
Grafana's dashboarding model is panel-based. Each panel is a single visualization — a time series graph, a stat, a gauge, a table, a heatmap, a logs panel, a Geomap — bound to one or more queries. Variables let you build dashboards that swap hosts, environments, or Kubernetes namespaces with a dropdown. Annotations overlay deploy events and incidents directly on your charts. Explore mode gives you an ad-hoc query workbench with automatic autocomplete for PromQL, LogQL, and FlashQL.
The Grafana ecosystem is sizeable. The official plugin catalog includes 150+ datasources and dozens of visualization plugins. The community dashboard library at grafana.com/grafana/dashboards lists 10,000+ ready-to-import dashboards covering everything from Node Exporter to Nginx, Cloudflare, MikroTik routers, and Kubernetes workloads.
Typical use cases include: infrastructure monitoring (CPU, memory, disk, network across a fleet), application performance monitoring (request latency percentiles, error rates, queue depths), log analytics (tailing and querying structured logs from Loki or Elasticsearch), business dashboards (revenue, signups, cohort charts pulled from PostgreSQL), SLO/SLA tracking (error budget burn rates), and network observability (SNMP polling of switches via Zabbix or Telegraf).
Why Self-Host Grafana on a VPS?
Running Grafana on your own VPS instead of subscribing to a SaaS observability product has real advantages:
- Flat-rate cost -- A VPS costs the same whether you ingest 1 GB or 1 TB of metrics. SaaS plans bill per series, per host, or per log GB, which can balloon unexpectedly.
- Data sovereignty -- Metrics, logs, and dashboards stay on infrastructure you control. Critical for GDPR, HIPAA, or contracts that forbid shipping operational data to third parties.
- Unlimited users -- Grafana OSS has no seat cap. Invite the whole engineering org, client stakeholders, or NOC staff with no per-user fees.
- Unlimited dashboards, datasources, and alerts -- No artificial feature gates.
- Full plugin access -- Install any community datasource or visualization plugin. Some enterprise-only plugins remain gated, but the OSS plugin catalog is large.
- Integration with local infrastructure -- Scrape internal services on your VPS network without opening holes to external SaaS collectors.
- Longer retention at lower cost -- Pair with Prometheus remote-write to VictoriaMetrics or Mimir for years of retention on cheap block storage, rather than paying per-GB-month SaaS retention tiers.
Grafana vs. Grafana Cloud vs. Datadog
| Feature | Self-Hosted Grafana OSS | Grafana Cloud Free | Grafana Cloud Pro | Datadog |
|---|---|---|---|---|
| Base cost | EUR 7.99-29.99/mo (VPS) | Free | Starts ~USD 49/mo | Starts USD 15/host/mo |
| Metrics cardinality | Unlimited (your storage) | 10k series | Per-series billing | Per-custom-metric billing |
| Log ingest | Unlimited (your storage) | 50 GB | USD 0.50/GB | USD 1.27/GB |
| Users | Unlimited | 3 | 3 free, then paid | Per-user billing |
| Dashboards | Unlimited | Unlimited | Unlimited | Unlimited |
| Plugins | OSS catalog + community | OSS + Enterprise plugins | OSS + Enterprise plugins | Datadog-only |
| SLA / support | Community | Community | 99.5% | 99.9% + 24x7 |
| Data location | Your VPS | Grafana Labs cloud | Grafana Labs cloud | Datadog cloud |
| Typical real-world monthly cost (5 hosts, ~30 dashboards, 100 GB logs) | EUR 7.99 | N/A (over free limit) | ~USD 150-250 | ~USD 300-500+ |
CloudCore Pricing Tiers for Grafana Workloads
| Tier | vCPU / RAM / NVMe | Monthly | Good For |
|---|---|---|---|
| Starter | 2 / 4 GB / 50 GB | EUR 7.99 | Single Grafana + small Prometheus or Loki, up to 20 dashboards |
| Professional | 6 / 12 GB / 100 GB | EUR 19.99 | Grafana + Prometheus + Loki + Alertmanager on one box |
| Business | 8 / 24 GB / 200 GB | EUR 39.99 | Multi-tenant Grafana, VictoriaMetrics, dozens of scraped targets |
| Enterprise | 16 / 64 GB / 500 GB NVMe | EUR 89.99 | Large orgs, long-retention metrics, heavy log ingest |
Prerequisites
Before you begin, make sure you have:
- A VPS running Ubuntu 24.04 LTS with root or sudo access
- SSH access to the server
- A domain name you can point at the server (for TLS and OAuth callbacks) — optional but strongly recommended
- At least 2 GB of RAM (4 GB recommended for production)
- At least 10 GB free disk for Grafana itself (Grafana's SQLite DB + logs + plugins stays small; the bulk of your storage will be consumed by your metrics/log backends)
Recommended Plan: Starter>
For a single-node Grafana with a small Prometheus or Loki alongside, the CloudCore Starter plan is a strong fit:>
- 2 vCPU cores
- 4 GB RAM
- 50 GB NVMe SSD
- Unmetered bandwidth
- EUR 7.99/month>
Grow into CloudCore Professional (6 vCPU, 12 GB RAM) when you add heavier backends like VictoriaMetrics or ship logs from multiple hosts.
Connect to your server via SSH to get started:
ssh root@your-server-ipStep 1: Update System Packages
Update the package index and upgrade installed packages. This keeps dependency resolution clean and picks up any kernel/security patches.
sudo apt update && sudo apt upgrade -yInstall a handful of prerequisites used by the repository setup:
sudo apt install -y apt-transport-https software-properties-common wget gnupgIf the upgrade changed the kernel, reboot before continuing:
sudo rebootStep 2: Add the Official Grafana APT Repository
Grafana provides an APT repository with GPG-signed packages. Import the signing key and add the stable channel:
sudo mkdir -p /etc/apt/keyrings
wget -q -O - https://apt.grafana.com/gpg.key | gpg --dearmor | sudo tee /etc/apt/keyrings/grafana.gpg > /dev/nullAdd the repository definition:
echo "deb [signed-by=/etc/apt/keyrings/grafana.gpg] https://apt.grafana.com stable main" | sudo tee /etc/apt/sources.list.d/grafana.listIf you want the beta channel (for testing upcoming releases), add beta main in addition to or instead of the stable line.
Refresh the package index:
sudo apt updateExpected output (abbreviated):
Get:1 https://apt.grafana.com stable InRelease [8,107 B]
Get:2 https://apt.grafana.com stable/main amd64 Packages [...]
Reading package lists... DoneStep 3: Install Grafana OSS
Install the open-source Grafana package:
sudo apt install -y grafanaExpected output (abbreviated):
The following NEW packages will be installed:
grafana
0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.
Need to get 95.4 MB of archives.
...
Adding system user grafana' (UID 113) ...
Adding new user grafana' (UID 113) with group grafana' ...</code></pre></div>The package installs:
Binary at /usr/sbin/grafana-server
CLI at /usr/sbin/grafana-cli
Configuration at /etc/grafana/grafana.ini
Data directory at /var/lib/grafana/ (including the default SQLite DB grafana.db)
Plugin directory at /var/lib/grafana/plugins/
Provisioning directory at /etc/grafana/provisioning/
Logs at /var/log/grafana/grafana.log
systemd unit at /lib/systemd/system/grafana-server.service
Verify the installed version:<div class="code-block" data-lang="bash"><div class="code-block__header"><span class="code-block__lang">bash</span></div><pre><code class="language-bash">grafana-server -v</code></pre></div>
Expected output:
<div class="code-block" data-lang="text"><div class="code-block__header"><span class="code-block__lang">text</span></div><pre><code class="language-text">Version 11.5.0 (commit: abc123, branch: HEAD)</code></pre></div>
Step 4: Start and Enable the grafana-server Service
Enable the service so it survives reboots, then start it:
<div class="code-block" data-lang="bash"><div class="code-block__header"><span class="code-block__lang">bash</span></div><pre><code class="language-bash">sudo systemctl daemon-reload
sudo systemctl enable grafana-server
sudo systemctl start grafana-server</code></pre></div>
Check the status:
<div class="code-block" data-lang="bash"><div class="code-block__header"><span class="code-block__lang">bash</span></div><pre><code class="language-bash">sudo systemctl status grafana-server</code></pre></div>
Expected output:
<div class="code-block" data-lang="text"><div class="code-block__header"><span class="code-block__lang">text</span></div><pre><code class="language-text">● grafana-server.service - Grafana instance
Loaded: loaded (/lib/systemd/system/grafana-server.service; enabled; preset: enabled)
Active: active (running) since Wed 2026-04-16 10:00:00 UTC; 3s ago
Main PID: 1234 (grafana)
Tasks: 9
Memory: 68.0M
CGroup: /system.slice/grafana-server.service
└─1234 /usr/sbin/grafana server --config=/etc/grafana/grafana.ini ...</code></pre></div>
Grafana listens on port 3000 by default. Visit http://your-server-ip:3000 in a browser. The default login is admin / admin. You will be forced to set a new password on first login — pick a strong one.
Step 5: Configure grafana.ini
The master config lives at /etc/grafana/grafana.ini. It is 1,500+ lines of commented defaults; rather than edit it in place, we recommend dropping override files into /etc/grafana/grafana.ini.d/ or editing only the sections below.
Open the config:
<div class="code-block" data-lang="bash"><div class="code-block__header"><span class="code-block__lang">bash</span></div><pre><code class="language-bash">sudo nano /etc/grafana/grafana.ini</code></pre></div>
At minimum, change these sections:
<div class="code-block" data-lang="ini"><div class="code-block__header"><span class="code-block__lang">ini</span></div><pre><code class="language-ini">[server]
protocol = http
http_port = 3000
domain = grafana.yourdomain.com
root_url = https://grafana.yourdomain.com/
enforce_domain = true
[security]
admin_user = admin
Set a strong initial password here, or set GF_SECURITY_ADMIN_PASSWORD via systemd env
cookie_secure = true
cookie_samesite = lax
strict_transport_security = true[users]
allow_sign_up = false
auto_assign_org = true
auto_assign_org_role = Viewer
[auth.anonymous]
enabled = false
[smtp]
enabled = true
host = smtp.yourmailhost.com:587
user = [email protected]
password = your-smtp-password
from_address = [email protected]
from_name = Grafana
[log]
mode = console file
level = info
[analytics]
reporting_enabled = false
check_for_updates = true</code></pre></div>
Key settings explained:
root_url — Must match the public URL users hit. Wrong values break OAuth callbacks, image rendering, and embedded panels.
enforce_domain = true — Redirects requests that come in via IP to the configured domain.
cookie_secure = true — Required once you are on HTTPS; prevents session cookies from leaking over plain HTTP.
allow_sign_up = false — Blocks random internet visitors from registering local accounts.
[auth.anonymous] enabled = false — Requires login for every dashboard unless you explicitly want public dashboards.
[smtp] — Needed for password reset emails and SMTP-based alert notifications.
Apply by restarting:<div class="code-block" data-lang="bash"><div class="code-block__header"><span class="code-block__lang">bash</span></div><pre><code class="language-bash">sudo systemctl restart grafana-server</code></pre></div>
Tail the log to catch any config errors:
<div class="code-block" data-lang="bash"><div class="code-block__header"><span class="code-block__lang">bash</span></div><pre><code class="language-bash">sudo tail -f /var/log/grafana/grafana.log</code></pre></div>
Step 6: Add Datasources (Prometheus, Loki, InfluxDB)
Grafana is useless without a datasource. You can add them in the UI under Connections -> Data sources, or declaratively via provisioning YAML.
Option A: UI
Log in at http://your-server-ip:3000.
Left sidebar -> Connections -> Data sources -> Add data source.
Pick the datasource type.
Fill in the URL and auth, click Save & test. Option B: Provisioning (Recommended)
Create /etc/grafana/provisioning/datasources/datasources.yaml:
<div class="code-block" data-lang="yaml"><div class="code-block__header"><span class="code-block__lang">yaml</span></div><pre><code class="language-yaml">apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://localhost:9090
isDefault: true
jsonData:
timeInterval: 15s
httpMethod: POST
- name: Loki
type: loki
access: proxy
url: http://localhost:3100
jsonData:
maxLines: 5000
derivedFields:
- datasourceUid: prometheus
matcherRegex: "traceID=(\\w+)"
name: TraceID
url: "$${__value.raw}"
- name: InfluxDB
type: influxdb
access: proxy
url: http://localhost:8086
jsonData:
version: Flux
organization: yourorg
defaultBucket: telegraf
tlsSkipVerify: false
secureJsonData:
token: YOUR_INFLUX_TOKEN</code></pre></div>
Restart Grafana to pick up provisioning changes:
<div class="code-block" data-lang="bash"><div class="code-block__header"><span class="code-block__lang">bash</span></div><pre><code class="language-bash">sudo systemctl restart grafana-server</code></pre></div>
Choosing a Metrics Backend
<div class="article-table-wrap"><table><thead><tr><th>Backend</th><th>Best For</th><th>Strengths</th><th>Considerations</th></tr></thead><tbody><tr><td><a href="/kb/install-guides/how-to-install-prometheus-ubuntu">Prometheus</a></td><td>Infra/app metrics, Kubernetes</td><td>Pull model, service discovery, PromQL</td><td>Single-node; pair with Thanos/Mimir for HA</td></tr><tr><td><a href="/kb/install-guides/how-to-install-victoriametrics-ubuntu">VictoriaMetrics</a></td><td>High-cardinality, long retention</td><td>10x+ more efficient than Prometheus</td><td>Less community ecosystem</td></tr><tr><td><a href="/kb/install-guides/how-to-install-influxdb-ubuntu">InfluxDB</a></td><td>IoT, push model, Flux queries</td><td>Great with Telegraf</td><td>v2/v3 migration path is bumpy</td></tr><tr><td><a href="/kb/install-guides/how-to-install-zabbix-ubuntu">Zabbix</a></td><td>Traditional NMS, SNMP, triggers</td><td>Built-in agent + vast template library</td><td>Grafana plugin is read-only</td></tr></tbody></table></div>
For logs, the obvious Grafana-native pairing is Loki. Loki uses LogQL (a close cousin of PromQL), stores compressed log streams cheaply, and integrates into the Grafana Explore view next to your metrics.Step 7: Import and Build Dashboards
Import a Community Dashboard by ID
The fastest way to get value from Grafana is to import a pre-built dashboard:
Left sidebar -> Dashboards -> New -> Import.
Enter the dashboard ID from grafana.com/grafana/dashboards — for example:
- 1860 — Node Exporter Full (host metrics)
- 3662 — Prometheus 2.0 Overview
- 12708 — Nginx exporter
- 13639 — Logs via Loki (basic)
- 15277 — Cadvisor Exporter
Click Load, pick your Prometheus datasource, Import. Provision Dashboards from JSON
For reproducible deployments, provision dashboards the same way you provision datasources.
Create /etc/grafana/provisioning/dashboards/dashboards.yaml:
<div class="code-block" data-lang="yaml"><div class="code-block__header"><span class="code-block__lang">yaml</span></div><pre><code class="language-yaml">apiVersion: 1
providers:
- name: 'default'
orgId: 1
folder: ''
type: file
disableDeletion: false
updateIntervalSeconds: 30
allowUiUpdates: true
options:
path: /var/lib/grafana/dashboards</code></pre></div>
Drop dashboard JSON files into /var/lib/grafana/dashboards/ (create the directory first, ensure grafana:grafana ownership). Grafana reloads them every 30 seconds.
Build a Custom Dashboard
Click Dashboards -> New -> New dashboard -> Add visualization. Pick a datasource, write a query (PromQL, LogQL, InfluxQL, Flux, or SQL depending on the source), choose a visualization type on the right panel, and set thresholds, units, and legend formatting. Use $__rate_interval in PromQL rate() calls for dashboards that work across different zoom levels.
Step 8: Configure Unified Alerting
Grafana's unified alerting (the default since v9) evaluates alert rules against any datasource and routes notifications through a built-in Alertmanager-compatible engine — or an external Alertmanager if you prefer.
Create a Contact Point
Alerting -> Contact points -> Add contact point.
Pick a type: Email, Slack, Telegram, Discord, PagerDuty, Opsgenie, or generic Webhook.
For email, Grafana uses the SMTP credentials you configured in grafana.ini.Create a Notification Policy
Notification policies route alerts to contact points based on labels. The default policy catches everything; add nested policies that match on severity=critical to escalate to on-call, and team=frontend to route to the right channel.
Create an Alert Rule
Alerting -> Alert rules -> New alert rule.
Set a query (e.g. avg(rate(node_cpu_seconds_total{mode!="idle"}[5m])) by (instance) > 0.9 for high CPU).
Set evaluation interval (1m) and the "for" duration (5m) — the rule must be breaching for 5 minutes before firing, which suppresses noise.
Add labels like severity=critical, team=platform.
Add an annotation summary: CPU at {{ $values.B.Value }} on {{ $labels.instance }}.
Pick the folder and evaluation group, then Save and exit. For larger setups, delegate routing and silencing to a standalone Alertmanager instance and point Grafana at it via Alerting -> Admin -> Alertmanager.
Step 9: Enable OAuth Login (Google & GitHub)
Password login is fine for a one-person setup, but for teams, OAuth is the right answer. You can configure Google, GitHub, GitLab, Okta, Auth0, Microsoft Entra, or any generic OIDC provider.
Google OAuth
In Google Cloud Console, create an OAuth 2.0 Client ID of type "Web application".
Authorized redirect URI: https://grafana.yourdomain.com/login/google.
Copy the Client ID and Client Secret.
Add to grafana.ini:<div class="code-block" data-lang="ini"><div class="code-block__header"><span class="code-block__lang">ini</span></div><pre><code class="language-ini">[auth.google]
enabled = true
name = Google
client_id = YOUR_CLIENT_ID.apps.googleusercontent.com
client_secret = YOUR_CLIENT_SECRET
scopes = openid email profile
auth_url = https://accounts.google.com/o/oauth2/v2/auth
token_url = https://oauth2.googleapis.com/token
api_url = https://openidconnect.googleapis.com/v1/userinfo
allowed_domains = yourdomain.com
allow_sign_up = true
auto_login = false</code></pre></div>
The allowed_domains directive restricts login to users whose Google email is at your company domain — essential for shared-tenant Google Workspace environments.
GitHub OAuth
In GitHub -> Settings -> Developer settings -> OAuth Apps -> New OAuth App.
Authorization callback URL: https://grafana.yourdomain.com/login/github.
Copy the Client ID, generate a new Client Secret.
Add to grafana.ini:<div class="code-block" data-lang="ini"><div class="code-block__header"><span class="code-block__lang">ini</span></div><pre><code class="language-ini">[auth.github]
enabled = true
name = GitHub
client_id = YOUR_GITHUB_CLIENT_ID
client_secret = YOUR_GITHUB_CLIENT_SECRET
scopes = user:email,read:org
auth_url = https://github.com/login/oauth/authorize
token_url = https://github.com/login/oauth/access_token
api_url = https://api.github.com/user
allowed_organizations = your-github-org
team_ids =
allow_sign_up = true</code></pre></div>
allowed_organizations gates login to members of your GitHub org. Use team_ids for finer control.
Restart Grafana to pick up the OAuth config:
<div class="code-block" data-lang="bash"><div class="code-block__header"><span class="code-block__lang">bash</span></div><pre><code class="language-bash">sudo systemctl restart grafana-server</code></pre></div>
Visit the login page — you should see new "Sign in with Google" and "Sign in with GitHub" buttons.
Step 10: Put Grafana Behind an Nginx Reverse Proxy
Nginx in front of Grafana gives you TLS, HTTP/2, request buffering, and the ability to firewall port 3000 off the public internet entirely.
Install Nginx and Certbot:
<div class="code-block" data-lang="bash"><div class="code-block__header"><span class="code-block__lang">bash</span></div><pre><code class="language-bash">sudo apt install -y nginx certbot python3-certbot-nginx</code></pre></div>
Create the site config at /etc/nginx/sites-available/grafana:
<div class="code-block" data-lang="nginx"><div class="code-block__header"><span class="code-block__lang">nginx</span></div><pre><code class="language-nginx">upstream grafana {
server 127.0.0.1:3000;
}
server {
listen 80;
server_name grafana.yourdomain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name grafana.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/grafana.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/grafana.yourdomain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options SAMEORIGIN;
client_max_body_size 20m;
location / {
proxy_pass http://grafana;
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;
}
# Grafana Live uses WebSockets — required for live dashboards and tail logs
location /api/live/ {
proxy_pass http://grafana;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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;
proxy_read_timeout 3600s;
}
}</code></pre></div>
Enable it and test the config:
<div class="code-block" data-lang="bash"><div class="code-block__header"><span class="code-block__lang">bash</span></div><pre><code class="language-bash">sudo ln -s /etc/nginx/sites-available/grafana /etc/nginx/sites-enabled/
sudo nginx -t</code></pre></div>
Before reloading Nginx, point your DNS A record for grafana.yourdomain.com at your server's IP. Then issue a Let's Encrypt certificate:
<div class="code-block" data-lang="bash"><div class="code-block__header"><span class="code-block__lang">bash</span></div><pre><code class="language-bash">sudo certbot --nginx -d grafana.yourdomain.com</code></pre></div>
Certbot will rewrite the config if needed, install the certificate, and set up automatic renewal via a systemd timer. Reload Nginx:
<div class="code-block" data-lang="bash"><div class="code-block__header"><span class="code-block__lang">bash</span></div><pre><code class="language-bash">sudo systemctl reload nginx</code></pre></div>
Now browse to https://grafana.yourdomain.com. Confirm the padlock, and that /api/live/ works by opening a dashboard with a live tail panel.
Because Grafana is now fronted by a proxy, tell it so in grafana.ini:
<div class="code-block" data-lang="ini"><div class="code-block__header"><span class="code-block__lang">ini</span></div><pre><code class="language-ini">[server]
protocol = http
http_addr = 127.0.0.1
http_port = 3000
domain = grafana.yourdomain.com
root_url = https://grafana.yourdomain.com/
serve_from_sub_path = false</code></pre></div>
Binding to 127.0.0.1 ensures the Grafana web server is unreachable from the public internet — only Nginx on the same host can reach it.
Restart Grafana:
<div class="code-block" data-lang="bash"><div class="code-block__header"><span class="code-block__lang">bash</span></div><pre><code class="language-bash">sudo systemctl restart grafana-server</code></pre></div>
Step 11: Harden with UFW
UFW (Uncomplicated Firewall) is the simplest way to lock down Ubuntu. Allow SSH and the Nginx ports, deny everything else:
<div class="code-block" data-lang="bash"><div class="code-block__header"><span class="code-block__lang">bash</span></div><pre><code class="language-bash">sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
sudo ufw status verbose</code></pre></div>
Expected output:
<div class="code-block" data-lang="text"><div class="code-block__header"><span class="code-block__lang">text</span></div><pre><code class="language-text">Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
To Action From
-- ------ ----
22/tcp (OpenSSH) ALLOW IN Anywhere
Nginx Full ALLOW IN Anywhere</code></pre></div>
Port 3000 is not open to the public — only Nginx's 127.0.0.1:3000 proxy connection reaches Grafana. Similarly, if Prometheus (9090), Loki (3100), or InfluxDB (8086) run on this server, their ports stay closed; Grafana talks to them over localhost.
If a colleague needs temporary access from a specific IP for debugging:
<div class="code-block" data-lang="bash"><div class="code-block__header"><span class="code-block__lang">bash</span></div><pre><code class="language-bash">sudo ufw allow from 203.0.113.42 to any port 3000 proto tcp</code></pre></div>
Troubleshooting
<div class="article-table-wrap"><table><thead><tr><th>Problem</th><th>Cause</th><th>Solution</th></tr></thead><tbody><tr><td><code>502 Bad Gateway</code> from Nginx</td><td>grafana-server not running or wrong upstream port</td><td><code>sudo systemctl status grafana-server</code>, check <code>/var/log/grafana/grafana.log</code>, verify <code>http_port</code> and upstream match</td></tr><tr><td>OAuth login loops or "redirect_uri mismatch"</td><td><code>root_url</code> in grafana.ini does not match OAuth callback</td><td>Set <code>root_url = https://grafana.yourdomain.com/</code> exactly, restart Grafana, update the OAuth app callback URL</td></tr><tr><td>Live dashboards / log tail stuck spinning</td><td>Nginx not proxying WebSockets on <code>/api/live/</code></td><td>Add the dedicated <code>location /api/live/</code> block with Upgrade/Connection headers</td></tr><tr><td>"Datasource is working" but panels show No Data</td><td>Datasource URL wrong, or time range outside ingested data</td><td>Check URL via curl from the Grafana host, widen the dashboard time range, verify datasource returns samples with <code>curl http://localhost:9090/api/v1/query?query=up</code></td></tr><tr><td>Password reset emails never arrive</td><td>SMTP misconfig or <code>[smtp] enabled = false</code></td><td>Tail <code>/var/log/grafana/grafana.log</code> during the request, verify SMTP credentials with <code>swaks</code> or similar</td></tr><tr><td><code>error="open /var/lib/grafana/grafana.db: permission denied"</code></td><td>File perms on data dir</td><td><code>sudo chown -R grafana:grafana /var/lib/grafana</code></td></tr><tr><td>Plugins fail to install with <code>unsupported operating system</code></td><td>Running grafana-cli as wrong user or behind a proxy</td><td><code>sudo grafana-cli plugins install <id></code>; for proxies, export <code>HTTP_PROXY</code> before running</td></tr><tr><td>Dashboards imported but show wrong datasource</td><td>Hard-coded datasource UID in JSON</td><td>Edit the JSON, replace the datasource UID with <code>${DS_PROMETHEUS}</code>, and define the variable in Dashboard Settings -> Variables</td></tr><tr><td>Cannot log in after enabling OAuth and <code>allow_sign_up = false</code></td><td>Local account disabled, OAuth user has no mapped org</td><td>Re-enable local login temporarily with <code>GF_AUTH_DISABLE_LOGIN_FORM=false</code>, log in as admin, then map users via <code>[users] auto_assign_org_role</code></td></tr></tbody></table></div>
Viewing Logs
The primary log stream is /var/log/grafana/grafana.log. Tail it live:
<div class="code-block" data-lang="bash"><div class="code-block__header"><span class="code-block__lang">bash</span></div><pre><code class="language-bash">sudo tail -f /var/log/grafana/grafana.log</code></pre></div>
Or via journald if you prefer:
<div class="code-block" data-lang="bash"><div class="code-block__header"><span class="code-block__lang">bash</span></div><pre><code class="language-bash">sudo journalctl -u grafana-server -f</code></pre></div>
Raise verbosity temporarily by setting level = debug in the [log] section of grafana.ini, then restart.
FAQ
What are the minimum hardware requirements to run Grafana?
Grafana itself is lightweight. A 2 vCPU, 2 GB RAM VPS runs Grafana OSS comfortably with a handful of datasources and dashboards. For production workloads with 10+ users, 20+ dashboards, and high-cardinality Prometheus queries, we recommend 2 vCPU / 4 GB RAM / 50 GB NVMe as a baseline. Remember that Grafana's resource footprint is dominated by concurrent dashboard queries; heavy panels that fan out into dozens of Prometheus instant queries can spike CPU briefly.
Is Grafana free to self-host?
Yes. Grafana OSS is open source under the AGPLv3 license and free to self-host on your own VPS with no user limits, dashboard limits, or feature flags. Grafana Cloud and Grafana Enterprise are commercial offerings with additional features (reporting, enterprise SSO, enterprise-only datasources like Oracle and Splunk, and managed backend services), but the core product covered in this guide costs nothing beyond your VPS.
Can I use Grafana without Prometheus?
Absolutely. Grafana supports dozens of datasources including InfluxDB, Loki, Elasticsearch, MySQL, PostgreSQL, CloudWatch, Azure Monitor, Google Cloud Monitoring, Tempo, Zabbix, TimescaleDB, and many more. Prometheus is the most popular pairing for metrics because it is open source, pull-based, and has best-in-class service discovery, but you can start with any supported backend. Many teams run InfluxDB + Telegraf for push-based metrics, Zabbix for traditional SNMP monitoring of networking gear, or straight PostgreSQL for business dashboards.
How does Grafana compare to Grafana Cloud and Datadog?
Self-hosted Grafana on a VPS runs at a flat monthly cost (typically EUR 7.99 to 29.99/month) with unlimited metrics, logs, users, and dashboards; the only growth cost is upgrading your VPS or adding storage. Grafana Cloud's free tier includes 10k metric series and 50 GB logs, then charges per active series and per ingested GB; it makes sense once you need managed HA, multi-tenant scale, or Grafana's enterprise plugins without running them yourself. Datadog is the most feature-complete APM + infrastructure + log platform on the market but bills per host, per custom metric, per log event, and per APM span; small teams typically reach hundreds of USD/month within the first year.
Should I put Grafana behind a reverse proxy?
Yes, for any production deployment. Grafana's built-in web server does not terminate TLS out of the box, and Nginx in front gives you Let's Encrypt certificates, HTTP/2, gzip/brotli compression, rate limiting, request buffering, and a single public entry point that you can firewall. It also lets you host Grafana alongside other apps on the same domain (e.g. monitoring.yourdomain.com -> Grafana, logs.yourdomain.com -> a Loki explorer) with a clean routing layer.
How do I back up Grafana?
Back up /var/lib/grafana/grafana.db (the default SQLite database containing users, dashboards, datasource config, API keys, and alert rules) plus /etc/grafana/ for the grafana.ini and provisioning YAML. Stop grafana-server briefly or use sqlite3 grafana.db ".backup /tmp/grafana.db.bak" for a consistent snapshot. For high availability, migrate from SQLite to PostgreSQL or MySQL (set [database] type = postgres in grafana.ini) and rely on your database server's standard backup tooling. Dashboards can also be exported as JSON and version-controlled in Git for reproducibility.
How do I upgrade Grafana?
Because you installed from the APT repository, upgrades are a routine apt operation:
<div class="code-block" data-lang="bash"><div class="code-block__header"><span class="code-block__lang">bash</span></div><pre><code class="language-bash">sudo apt update
sudo apt install --only-upgrade grafana
sudo systemctl restart grafana-server</code></pre></div>
Always read the release notes at grafana.com/docs/grafana/latest/whatsnew/ before jumping major versions — Grafana 9 introduced unified alerting (migrating legacy alerts), Grafana 10 dropped several deprecated APIs, and Grafana 11 tightened auth defaults.
Can I run Grafana in Docker instead?
Yes. docker run -d -p 3000:3000 --name grafana -v grafana-storage:/var/lib/grafana grafana/grafana-oss` gives you a working instance in under a minute. The apt install covered in this guide is preferred for production single-host setups because systemd integration, log rotation, and package upgrades are all familiar Ubuntu workflows. Docker shines for ephemeral test environments, multi-instance setups behind Traefik, or when you are already deep in Docker Compose.
Next Steps
With Grafana up and running on your VPS, these are the highest-leverage follow-ups:
- Install Prometheus — The classic pairing for infrastructure metrics. Add Node Exporter on every host to feed CPU, memory, disk, network, and process stats into Grafana.
- Install Loki — Grafana-native log aggregation. Pair with Promtail or Vector to ship logs from every host; query with LogQL in the same Explore view as your metrics.
- Install InfluxDB — Push-based time-series database. Perfect if you are using Telegraf for IoT, network, or application metrics.
- Install VictoriaMetrics — A drop-in Prometheus remote-write target that handles 10x+ higher cardinality at a fraction of the RAM. The path forward when your Prometheus starts struggling.
- Install Zabbix — Traditional network monitoring with SNMP, agents, and trigger-based alerts. Its Grafana plugin lets you embed Zabbix panels alongside Prometheus metrics.
- Install Alertmanager — Standalone Alertmanager for large deployments, with deduplication, grouping, silencing, and routing into Slack, PagerDuty, Opsgenie, and webhooks.
- Install Nginx properly — If you skipped the reverse proxy step, revisit it. Nginx pays for itself the first time you need to host a second service on the same box.
- Read the Grafana documentation — The official docs are thorough, well-organized, and kept up to date with every release.
Prefer a clean Ubuntu base to deploy on?
>
Our CloudCore Starter VPS (EUR 7.99/month) gives you a clean Ubuntu 24.04 box ready for this install. Grow into Professional, Business, or Enterprise tiers as your metrics footprint grows — same OS image, bigger engine.
>
- 2 vCPU / 4 GB RAM / 50 GB NVMe
- Unmetered bandwidth
- Root SSH, snapshots, rebuilds
- Data center choices in the EU, US, and APAC
>
Deploy Your VPS — and have Grafana live in under 30 minutes.