Skip to main content
RunBook Academy

ObservabilityXXIV · Grafana InstallationGrafanaInstall

Initial Admin and Authentication

Intermediate⏱ ~18 minbash

What you'll learn

  • Explain why the first Grafana login must be performed with an explicit admin user rather than the anonymous-admin path
  • Set the initial admin password through the environment variable GF_SECURITY_ADMIN_PASSWORD__FILE for containers and through [security] for host installs
  • Disable anonymous access and configure cookie / header auth for a real install
  • Distinguish the Server Admin role from the org Admin role and state when each is required
  • Audit an existing install for the admin / admin default and remediate the discovery

Prerequisites

Verified against Prometheus 2.55.x · Alertmanager 0.28.x · node_exporter 1.8.x · blackbox_exporter 0.26.x · Grafana 11.x · Loki 3.x · Tempo current · OpenTelemetry Collector 0.110.x · Grafana Alloy current · Docker Engine 28.x · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL / Rocky / AlmaLinux 9.x · 2026-08-13

Not yet marked complete on this device.

A new Grafana is reachable on the open internet for ten minutes. Two of those minutes were spent curling /api/health, four were spent configuring the data sources, four were spent looking for the admin password. In those ten minutes a scanner found the default admin / admin login. The scanner logged in. The scanner created an API key. The scanner exported every dashboard. The audit log records the source IP of the scanner, not the team’s actual admin activity, because no admin activity exists yet. The first hour of the new install is spent recovering from a credential leak that should have been a non-event.

The first admin user is the smallest piece of config in this module and the single largest piece of risk. The defaults are chosen for the friendly first-run experience; the defaults are also the most-published credential pair in the world.

What it is

“Initial admin and authentication” is the configuration that establishes who can do what when the Grafana instance first runs, and how every later user is going to authenticate. In Grafana 11.x it covers:

  • The default admin user created on first boot, with a password sourced from the environment, the [security] section, or a default the operator must override.
  • The [security] knobs that disable anonymous access, set cookie / session parameters, and define the basic-auth / OAuth / SAML / LDAP providers.
  • The role model: Server Admin, Org Admin, Editor, Viewer, and the implicit hierarchy of permissions each role grants on dashboards, folders, datasources, and the alerting engine.
  • The login URL, the login attempt throttles, and the brute-force protections ([security.brute_force_*]).
   First boot of grafana-server
              |
              v
   +--------------------+
   | Is the database    |--no--> run schema migrations
   | migrated?          |        then create schema
   +---------+----------+
             |
             v
   +--------------------+
   | Does an admin user |--yes--> keep running; admin/admin
   | exist?             |        is NOT used
   +---------+----------+
             |
             v no
   +--------------------+
   | Create the first   |
   | admin user         |
   +---------+----------+
             |
             v
   +--------------------+
   | Read the initial   |    GF_SECURITY_ADMIN_PASSWORD
   | admin password     |    GF_SECURITY_ADMIN_PASSWORD__FILE
   | from:              |    [security] admin_password
   +---------+----------+
             |
             v
   +--------------------+
   | Apply auth provider |
   | chain: [auth]      |
   +--------------------+

Two credentials cooperate: the first admin user (a real account in the database) and the [auth] provider chain that governs later authentication. The first admin is always created in the database regardless of what providers come later.

Why a sysadmin cares

The initial admin is the only account that exists before any provider is configured. It is also the account a scanner knows about before your team does. Three reasons to take this seriously:

  • Default admin / admin is published. It is documented and it is on every scanner list. A Grafana exposed to a hostile network without a custom password is compromised inside the ten-minute window described above.
  • The first admin is unrecoverable through any other channel. Once the database is provisioned with an OAuth provider, basic-auth credentials cannot be reset through the OAuth provider. If the operator only knows the SAML metadata and not the first admin password, that password is only recoverable through a database-level reset.
  • The first admin is the bypass. Anyone with the admin password has the Server Admin role; the Server Admin role can disable the auth providers, create new users, and read audit logs. A scanner that captures this credential sees everything.

How it works: the role and the providers

Grafana 11.x models users, orgs, and roles as a small RBAC hierarchy:

   Server Admin (Grafana-wide)
      |  -- sees every org, every user
      |  -- can configure auth providers, LDAP mappings
      |
   +-- Org Admin (per organisation)
          |  -- manages users within the org
          |  -- manages org-level datasources and alerting
          |
   +-- Editor (per organisation)
          |  -- edits dashboards and alerts
          |
   +-- Viewer (per organisation)
              -- read-only on dashboards

admin is a username, not a permission. The first user created is granted the Server Admin role implicitly. Subsequent users are created by an admin or by a configured provider with a chosen role.

Authentication is provider-chained. Grafana walks the list and the first provider that recognises the user takes precedence:

   [auth] providers
      basic_auth        -- always available
      oauth             -- GitHub / GitLab / Google / Azure AD ...
      saml              -- enterprise SSO
      ldap              -- directory service
      proxy             -- header-based (X-WEBAUTH-USER)
      anonymous         -- dev / local only; DEFAULT-DISABLED in 11.x

The first admin is always created in the database regardless of provider order. Setting [auth.anonymous] enabled = true re-enables a default-disabled feature; leaving it enabled in production is the lesson’s “anonymous admin is a security failure” point.

How to configure it

Host install (apt / rpm / tarball): environment variable

The first admin password can be set as an environment variable that Grafana reads at boot. The recommended form is the __FILE variant, which reads the secret from a file rather than the process environment:

# /etc/grafana/grafana.env (mode 0640, owner grafana:grafana)
GF_SECURITY_ADMIN_PASSWORD__FILE=/etc/grafana/admin_password

# /etc/grafana/admin_password (mode 0640, owner grafana:grafana)
# Note: leading newline is fine; Grafana strips whitespace.
cat /etc/grafana/admin_password
# <a high-entropy string the operator generated>

The systemd drop-in from lesson 03 reads this:

# /etc/systemd/system/grafana-server.service.d/20-admin.conf
[Service]
EnvironmentFile=/etc/grafana/grafana.env

Container install: GF_SECURITY_ADMIN_PASSWORD

# CONFIGURATION: write the password into a docker secret or a
# bind-mounted file. Never `-e GF_SECURITY_ADMIN_PASSWORD=...`
# on a public command line.
docker run -d \
  --name grafana \
  --restart=unless-stopped \
  -p 3000:3000 \
  -v grafana-data:/var/lib/grafana \
  -v /etc/grafana:/etc/grafana:ro \
  -e "GF_SECURITY_ADMIN_PASSWORD__FILE=/run/secrets/grafana_admin" \
  grafana/grafana:11.3.0

grafana.ini for the auth chain

# /etc/grafana/grafana.ini
[security]
# Disable the legacy default user creation path. The first admin
# user still exists, but the implied "anonymous can read public
# dashboards" stays off.
allow_embedding = false
# Cookie parameters
cookie_secure   = true       # only sent over TLS
cookie_samesite = lax
session_life_timeout = 24h
# Brute force protection
discover_basic_auth = false
brute_force_enabled = true
brute_force_attempts_before_lockout = 5

[users]
allow_sign_up = false
auto_assign_org = true
auto_assign_org_role = Viewer

[auth]
# Auth provider order. The first provider that recognises the user
# wins; basic_auth is the fallback.
login_maximum_inactive_lifetime_days = 7
login_maximum_lifetime_days = 30

[auth.anonymous]
# Refuse to enable anonymous access.
enabled = false

[auth.basic]
enabled = true

# OAuth / SAML / LDAP blocks each carry their own [auth.<provider>]
# section; see the references linked at the top of this lesson.

First-login procedure

# READ-ONLY: confirm Grafana is up.
curl -fsS http://127.0.0.1:3000/api/health
# {"database":"ok","version":"11.3.0"}

# CONFIGURATION: change the default admin password.
# Generate the replacement rather than typing one in, and record it
# in the team password manager before you run the request.
NEW_ADMIN_PW="$(openssl rand -base64 24 | tr -d '=/+')"

# The first login uses the GF_SECURITY_ADMIN_PASSWORD value.
curl -fsS -u admin:"${GF_SECURITY_ADMIN_PASSWORD}" \
  -H "Content-Type: application/json" \
  -X PUT http://127.0.0.1:3000/api/admin/users/2/password \
  -d "$(jq -n --arg p "${NEW_ADMIN_PW}" '{password: $p}')"

Initial password rotation for the “discovered admin / admin” case

# SERVICE-IMPACT: this rotates the admin password.
NEW_PW="$(openssl rand -base64 24 | tr -d '=/+')"
echo "${NEW_PW}" > /etc/grafana/admin_password
chmod 0640 /etc/grafana/admin_password
chown grafana:grafana /etc/grafana/admin_password

# Restart so grafana-server re-reads the password file.
sudo systemctl restart grafana-server

# Verify by logging in with the new password.
curl -fsS -u admin:"${NEW_PW}" http://127.0.0.1:3000/api/org | jq .name
# "Main Org."

For Docker, replace the rotation with a docker rm grafana && docker run ... while bound to the same named volume. The admin password file path inside the container still applies.

How to validate it

# READ-ONLY: anonymous access is OFF.
curl -fsS -I http://127.0.0.1:3000/api/dashboards/home
# HTTP/1.1 401 Unauthorized

# READ-ONLY: the first admin account exists and accepts the password.
curl -fsS -u admin:"${GF_SECURITY_ADMIN_PASSWORD}" \
  http://127.0.0.1:3000/api/org
# {"id":1,"name":"Main Org.","address":...}

# READ-ONLY: Server Admin role is the only Server Admin.
curl -fsS -u admin:"${GF_SECURITY_ADMIN_PASSWORD}" \
  http://127.0.0.1:3000/api/admin/users | jq '.[] | select(.isGrafanaAdmin==true)'
# {"id":1,"login":"admin",...}

# READ-ONLY: the auth provider chain is what we expect.
curl -fsS -u admin:"${GF_SECURITY_ADMIN_PASSWORD}" \
  http://127.0.0.1:3000/api/admin/settings \
  | jq '.auth | keys'
# [
#   "anonymous",
#   "basic",
#   "github",
#   "oauth"
# ]

# READ-ONLY: brute-force throttles are on.
curl -fsS -u admin:"${GF_SECURITY_ADMIN_PASSWORD}" \
  http://127.0.0.1:3000/api/admin/settings \
  | jq '.security | {brute_force_enabled, brute_force_attempts_before_lockout}'
# {"brute_force_enabled":true,"brute_force_attempts_before_lockout":5}

# READ-ONLY: no extra admin account with login "admin" and a weak
# password exists (defence-in-depth check).

A clean validation: 401 on /api/dashboards/home without a credential, the admin user logs in with the env-supplied password and is Server Admin, anonymous is disabled, brute-force throttles are on.

How it can fail

The high-frequency failure modes here are mostly misconfigurations that silently leave an install open.

  1. admin / admin left from a friendly first-run. The default is documented and reachable. A scanner finds it inside ten minutes. The visible symptom is API keys in the audit log that the team did not create.
  2. auth.anonymous enabled to “test the dashboards”. A config set during early testing is forgotten. The visible symptom is /api/dashboards/home returning 200 without credentials in production.
  3. GF_SECURITY_ADMIN_PASSWORD left as the literal admin in a docker-compose file checked into git. The visible symptom is docker inspect on the running container shows the password in cleartext. Anything with commit history has the password forever.
  4. Cookie session timeout left at one week. A long session lifetime on a Grafana accessible from a kiosk or shared workstation keeps a session open after the user logged out physically. The visible symptom is active sessions in the audit log that no operator can explain.
  5. OAuth configured without Server Admin escape hatch. A team wires OAuth, removes the basic-auth provider, and finds that the OAuth provider is rejecting everyone. The visible symptom is no one can log in to revert the change. The fix is a database-level reset of the admin user’s password.
  6. Brute-force throttles off. The visible symptom is a spike of failed /login requests in the Grafana log with no back-off and the /api/org token endpoint draining against host CPU.

How to troubleshoot it

  1. Confirm Grafana ran with the expected env var. cat /proc/$(pidof grafana-server)/environ | tr '\0' '\n' | grep -i admin — empty output means the env was not loaded.
  2. Confirm the admin password file exists and is readable. sudo -u grafana cat /etc/grafana/admin_password returns the password that Grafana is reading.
  3. Reset the admin password through the database if all else fails. The bcrypt hash is in the users table; a known-hash update unlocks the account.
  4. Confirm auth provider order in /api/admin/settings under auth.providers.
  5. Check the journal for failed login back-offs: journalctl -u grafana-server | grep -i 'user login'.
  6. For container installs, compare docker inspect env to the secret file contents in the running container.

Security implications

  • First-admin escape hatch. Even with every provider configured, the basic-auth admin user must exist as the fall-through. Deleting the row is a lockout scenario.
  • cookie_secure = true. Without it, the session cookie can be set over cleartext (lesson 04 covers TLS).
  • cookie_samesite = lax prevents cross-origin CSRF on the dashboard JSON endpoints.
  • allow_sign_up = false prevents a brand-new Grafana with no configured provider from accepting self-sign-up.
  • discover_basic_auth = false prevents the Authorization: Basic header on cross-origin preflights from causing credential-stuffing attempts.
  • Audit log. Every login (success and failure) is recorded in the audit log under the database. The retention is in [audit] and lives in the same database Grafana does.

Performance implications

  • bcrypt cost. The default bcrypt cost is 10; each login takes ~80 ms. Brute-force throttles mean this is not the CPU bottleneck; a coordinated credential-stuffing is.
  • Session storage. Sessions are stored in the database table user_session. A busy Grafana accumulates sessions until the configured [users] user_session_max_idle_days cleans them up. A Grafana that keeps [users] user_session_max_idle_days at the default of 30 will have hundreds of thousands of stale rows by year three.
  • Login-throttled requests. The brute-force throttles are in-memory; a busy install does not pay a database round-trip per failed login attempt.

Production guidance

  • Treat the first admin password as a credential, not a config. Source it from a secret store, inject it through GF_SECURITY_ADMIN_PASSWORD__FILE, and rotate it according to the same cadence as every other admin credential.
  • Set [auth.anonymous] enabled = false explicitly. Do not rely on the default; document the choice in the runbook.
  • Always keep basic_auth enabled even when other providers are configured. The admin escape hatch has saved every large Grafana install once.
  • Set cookie_secure = true and the proxy terminates TLS (lesson 04). A Grafana that serves cleartext behind a HTTPS proxy will set insecure cookies and the browser session will silently break.
  • Audit the install for the default admin / admin pair on day one. A scanner reaches the host before the team does.

Verification

You should now be able to answer:

  • Why is admin / admin a credential that any scanner can find, and what is the first configuration change that defends a new install?
  • What is the difference between [auth.anonymous] enabled = false and removing the user “admin”?
  • When would a team accept the basic-auth escape hatch persisting in production, and when would they remove it?
  • What does cookie_secure = true actually do, and what configuration depends on it being true?

Quiz

Knowledge check · 8 questions

  1. Q1. Where should the initial Grafana admin password live in a production install?

  2. Q2. `[auth.anonymous] enabled = true` is an acceptable configuration for a Grafana that is exposed to the open internet behind a reverse proxy, as long as dashboards are non-sensitive.

  3. Q3. Which settings in [security] should be ON for a production Grafana behind a TLS-terminating proxy?

  4. Q4. Why does a production Grafana almost always keep `[auth.basic] enabled = true` even when OAuth or SAML is configured?

  5. Q5. Name the credential pair that the published Grafana install defaults to if the operator does not override it.

  6. Q6. A Grafana instance has just been migrated to OAuth. The configured SAML provider has rejected every user for the past two hours, and the team needs to revert. What is the right move?

  7. Q7. A `[security] admin_password = admin` line in grafana.ini is a defensible production default; it is only changed once a real user logs in.

  8. Q8. A user reports that after logout, the next browser open of the Grafana URL is already authenticated. Which setting is most likely too generous?

Passing score: 75%. Answers are checked in this browser.