ObservabilityXXIV · Grafana InstallationGrafanaInstall
Initial Admin and Authentication
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
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
adminuser 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 / adminis 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.
admin / adminleft 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.auth.anonymousenabled to “test the dashboards”. A config set during early testing is forgotten. The visible symptom is/api/dashboards/homereturning 200 without credentials in production.GF_SECURITY_ADMIN_PASSWORDleft as the literaladminin a docker-compose file checked into git. The visible symptom isdocker inspecton the running container shows the password in cleartext. Anything with commit history has the password forever.- 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.
- 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.
- Brute-force throttles off. The visible symptom is a
spike of failed
/loginrequests in the Grafana log with no back-off and the/api/orgtoken endpoint draining against host CPU.
How to troubleshoot it
- 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. - Confirm the admin password file exists and is readable.
sudo -u grafana cat /etc/grafana/admin_passwordreturns the password that Grafana is reading. - Reset the admin password through the database if all
else fails. The bcrypt hash is in the
userstable; a known-hash update unlocks the account. - Confirm auth provider order in
/api/admin/settingsunderauth.providers. - Check the journal for failed login back-offs:
journalctl -u grafana-server | grep -i 'user login'. - For container installs, compare
docker inspectenv to the secret file contents in the running container.
Security implications
- First-admin escape hatch. Even with every provider
configured, the basic-auth
adminuser 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 = laxprevents cross-origin CSRF on the dashboard JSON endpoints.allow_sign_up = falseprevents a brand-new Grafana with no configured provider from accepting self-sign-up.discover_basic_auth = falseprevents theAuthorization: Basicheader 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_dayscleans them up. A Grafana that keeps[users] user_session_max_idle_daysat 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 = falseexplicitly. Do not rely on the default; document the choice in the runbook. - Always keep
basic_authenabled even when other providers are configured. The admin escape hatch has saved every large Grafana install once. - Set
cookie_secure = trueand 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 / adminpair on day one. A scanner reaches the host before the team does.
Verification
You should now be able to answer:
- Why is
admin / admina 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 = falseand 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 = trueactually do, and what configuration depends on it being true?
Quiz
Knowledge check · 8 questions
Q1. Where should the initial Grafana admin password live in a production install?
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.
Q3. Which settings in [security] should be ON for a production Grafana behind a TLS-terminating proxy?
Q4. Why does a production Grafana almost always keep `[auth.basic] enabled = true` even when OAuth or SAML is configured?
Q5. Name the credential pair that the published Grafana install defaults to if the operator does not override it.
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?
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.
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.