How to show a private Grafana dashboard on a TV without a login screen
Grafana kiosk mode hides the menus but does not sign in for you, so a wallboard eventually shows a login page. Here are the three ways that actually work to display a non-public Grafana dashboard on a TV or Raspberry Pi with no login, the popular one that does not, the exact settings for each, and how to keep the screen alive afterwards.
Why a Grafana wallboard ends up on the login page
A dashboard you open in your own browser works because your browser holds a Grafana session cookie. A TV, a Raspberry Pi or any kiosk browser has no such session, and even if someone logs in once on the device, Grafana sessions end after seven days of inactivity and after 30 days at most (the [auth] lifetime settings), and the screen drops back to Welcome to Grafana. Adding ?kiosk to the URL changes what is drawn, not who is allowed to see it.
The fix is always the same idea: give the screen a way to view the dashboard that does not depend on a person's session. Grafana offers three that work, and one popular suggestion that does not; they differ in where they work, what they support and how much they expose.
Before you start
- Org admin rights in Grafana, or server admin for methods 2 and 3.
- A dashboard you are happy to show on a wall.
- For method 3: a reverse proxy such as Caddy or nginx in front of Grafana.
- A supported Raspberry Pi and a free DisplayOps account.
When you are done
- A URL that opens the dashboard with no login, menus hidden, panels refreshing.
- Nothing on the screen that is worth stealing: a link, an anonymous org or an allow-listed address.
- Optionally a password on the route that only your screens know.
- A screen that reloads, recovers and reports back by itself.
Choose a method
| Method | Works on | Who sets it up | Login prompts | Best for | Caveat |
|---|---|---|---|---|---|
| Shared dashboard link | Grafana Cloud, OSS and Enterprise 10.0+ (called public dashboards before 11) | Org admin, or a role with the dashboards.public:write permission | Never | Most teams. Safest option, no server config. | No template variables; the link works for anyone who has it; auto-refresh is not guaranteed on the shared view. |
| Anonymous viewer org | Self-hosted OSS and Enterprise | Server admin (grafana.ini) | Never | Dashboards with variables, on a private network. | Not available on Grafana Cloud; counts as active users on Enterprise. |
| JWT URL login (auth_token in the URL) | Self-hosted only | Server admin (grafana.ini) | The first page loads, then the panels fail | Nothing on a kiosk. Listed because it is widely recommended and does not work in current Grafana. | Only the request carrying the token is authenticated; no session is created, so the dashboard's data requests are rejected (Grafana 10.2.8 and newer). |
| Auth proxy | Self-hosted, behind your own reverse proxy | Server admin and proxy admin | Never for allow-listed screens | Self-hosted Grafana with variables and full features, screens on known addresses or a VPN. | Grafana must only be reachable through the proxy, and the proxy must strip the header from everyone else. |
Rule of thumb: on Grafana Cloud use a shared link. Self-hosting, use anonymous access with a dedicated org when the dashboards can be public inside your network, and the auth proxy when they cannot.
Method 1Share the dashboard with a link
Grafana calls this externally shared dashboards in current versions and public dashboards in older ones (public dashboards became generally available in Grafana 10.0). It produces a read-only URL that runs the dashboard's stored queries for anyone who has the link, with no login. It is the safest option because the screen holds no credential at all, and it is the only option on Grafana Cloud. You need to be an organisation admin, or hold the dashboards.public:write permission, to create one.
Make a copy for the wall
Open the dashboard, choose Save as copy, and name it, for example,
NOC wall. Shared links do not support template variables, so in the copy replace each variable with the fixed value the wall should show. Remove panels that use frontend data sources or library panels; they render empty on a shared link.Share it
Click the Share dropdown at the top of the dashboard and choose Share externally. On Grafana 10 the same option is the Public dashboard tab of the Share dialog.
Choose who can open it
Select Anyone with the link, tick the acknowledgement checkbox and click Accept. The Only specific people option emails one-time links that expire; it is meant for humans, not screens.
Set the options
Leave Enable time range off so the wall cannot be changed by whoever finds the link, and turn Display annotations on only if the wall should show them.
Copy the link
https://grafana.example.com/public-dashboards/8f2c…The shared view already has no menus, so it needs no
kioskparameter. Auto-refresh is not part of the shared view, so refresh from the screen instead: in DisplayOps set the display's Auto refresh to one to five minutes (see below).Later: pause or revoke
Pause access stops the link temporarily and keeps the URL; Revoke access kills it for good and you must share again.
Shared dashboards run only the queries saved in the dashboard. Template variables and queries that use them, frontend data sources, library panels, exemplars, Grafana Live streams, organisation annotations and data sources that use reverse-proxy functionality are not supported, and tag-based annotation queries return matching annotations from every dashboard in the org. Anyone who obtains the link can view the data until you revoke it, so treat the URL like a password.
Method 2An anonymous viewer organisation (self-hosted)
Anonymous access lets unauthenticated visitors view one organisation with a role you choose. It supports everything a normal viewer sees, including template variables, playlists and links between dashboards. It is a server setting in grafana.ini, available on self-managed OSS and Enterprise only, not on Grafana Cloud. On Grafana Enterprise anonymous devices count towards licensed users, one user per three devices.
Create a dedicated org
AdministrationGeneralOrganizations+ New org, for example
Wall. Note its ID in the list; you need it for the URL. Anonymous visitors will see this org only, so never enable anonymous access on the org that holds everything.Move or import the wall dashboards
Into that org, along with read-only data source connections. Export as JSON from the main org and import into
Wall; point the data sources at read-only credentials.Enable anonymous access
In
grafana.ini(or the matchingGF_AUTH_ANONYMOUS_*environment variables in Docker), then restart Grafana:grafana.ini [auth.anonymous] enabled = true org_name = Wall org_role = Viewer hide_version = true # optional: cap how many anonymous devices may connect device_limit = 20Build the URL
With the org id of
Wall:https://grafana.example.com/d/abc123/noc?orgId=2&kiosk&refresh=30sOpen it in a private browser window: no login is asked.
Restrict who can reach Grafana
If it is on the internet: an allow-list on your reverse proxy or firewall for the office and the screens, or keep Grafana on the private network and let the screens sit on that network.
Everything in the Wall org is visible to any device that can reach the Grafana URL. Put only wall content in that org, use read-only data source users, and prefer Method 1 if the instance is reachable from the internet.
Method 3Auth proxy behind your own reverse proxy (self-hosted)
With auth proxy, Grafana trusts a header such as X-WEBAUTH-USER set by a reverse proxy in front of it. If your screens have fixed addresses on the office network, the proxy can inject a viewer username for requests from those addresses only, and everyone else still logs in normally. Nothing is stored on the screen and no link can leak.
Enable the auth proxy
In
grafana.ini;whitelistmust be the proxy's address so a spoofed header from anywhere else is ignored:grafana.ini [auth.proxy] enabled = true header_name = X-WEBAUTH-USER header_property = username auto_sign_up = true whitelist = 10.0.0.5 enable_login_token = falseConfigure the proxy
In Caddy, for example, match the screens' addresses and set the header. Every other client must have the header removed, otherwise anyone could add it themselves and become the wall viewer:
Caddyfile grafana.example.com { @screens remote_ip 10.0.20.11 10.0.20.12 reverse_proxy @screens grafana:3000 { header_up X-WEBAUTH-USER wall-viewer } reverse_proxy grafana:3000 { header_up -X-WEBAUTH-USER } }Make sure Grafana is reachable only through the proxy
Bind it to localhost or a private interface. If Grafana's own port is exposed, anyone can send the header themselves.
Screens outside the office: swap the allow-list for basic auth
Protect the screens' route with the proxy's basic auth (
basic_authin Caddy,auth_basicin nginx) and setX-WEBAUTH-USERfor requests that pass it. Store that username and password on the DisplayOps content item and the screen answers the prompt itself, from anywhere over HTTPS. The HTTP authentication guide has the proxy snippets.Create the viewer
Open the dashboard once from a screen's address, then assign the auto-created user read-only permissions on the wall folder.
Why JWT URL login (auth_token) is not the answer
Grafana can authenticate a request with a signed JSON Web Token, and with url_login = true it also accepts the token as the auth_token query parameter. Many guides present this as the kiosk solution. It is not, and we checked the current source before saying so: the JWT client authenticates the single request that carries the token and removes the parameter from the URL; nothing creates a session. The dashboard page renders, but every data request the page then makes arrives without a token and is rejected, so the panels stay empty or the page bounces to the login screen. Older releases created a session cookie here; Grafana 10.2.8 and newer do not (tracked as grafana/grafana#90200).
JWT auth still has two legitimate uses for screens: a reverse proxy that adds the token as a header on every request, which is the auth proxy method above with a signed token instead of a plain username, and header injection by the player itself, which is on the DisplayOps roadmap. If you already configured [auth.jwt], keep the key material; it will plug straight into either.
What the configuration looks like, for the proxy or header-injection case
Create a signing key pair
On your workstation. Grafana verifies tokens with the public key; the private key never leaves your machine.
terminal openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out jwt-private.pem openssl rsa -in jwt-private.pem -pubout -out jwt-public.pemInstall the public key
On the Grafana host, for example
/etc/grafana/jwt-public.pem, readable by the Grafana user.Enable JWT auth
In
grafana.iniand restart Grafana. Users are created on first sight with thesubclaim as their login, andexpect_claimsrejects tokens minted for anything else.grafana.ini [auth.jwt] enabled = true header_name = X-JWT-Assertion url_login = true username_claim = sub email_claim = email key_file = /etc/grafana/jwt-public.pem auto_sign_up = true expect_claims = {"iss": "wall-displays"} # role for auto-created users; Viewer is the default in most installs # auto_assign_org_role = Viewer (in the [users] section)Mint a token
For a dedicated user, with the expiry you want the screen to keep working. One year is common for a wallboard; shorter if the dashboard is sensitive. This uses PyJWT (
pip install pyjwt cryptography):mint.py import jwt, time claims = { "sub": "wall-noc-1", "email": "wall-noc-1@example.com", "iss": "wall-displays", "iat": int(time.time()), "exp": int(time.time()) + 365 * 24 * 3600, } print(jwt.encode(claims, open("jwt-private.pem").read(), algorithm="RS256"))Send it as a header, not in the URL
Configure your reverse proxy to add
X-JWT-Assertion: <token>for the screens' addresses (the same shape as the Caddy example in the auth proxy method, with this header instead ofX-WEBAUTH-USER) and to strip that header from everyone else. Grafana then authenticates every request the dashboard makes.Check the user
After the first load, AdministrationUsers and accessUsers shows the auto-created user; confirm it is a Viewer in the right org and can see the wall folder.
Grafana's documentation warns that a token in a URL can end up in logs and be hijacked without TLS. Grafana does not support refresh tokens, so when a token expires the screen shows the login page; put the expiry date in your calendar or in the display's name.
Kiosk URL parameters worth knowing
Whichever method you chose, the URL you give the screen should describe exactly what to show. Grafana reads these query parameters. Grafana 13 turned dynamic dashboards on for everyone; the parameters below are unchanged there.
| Parameter | What it does |
|---|---|
kiosk | Hides the navigation and side menu. Use it bare (kiosk=1 is ignored). Since Grafana 11.3 the dashboard controls stay visible in kiosk mode until you add the _dash parameters below. kiosk=tv, the older "TV mode", still exists in some versions but behaves inconsistently; do not rely on it. |
_dash.hideTimePicker=true | Hides the time and refresh picker in kiosk mode (Grafana 11.3 and newer). |
_dash.hideVariables=true | Hides the variable and annotation controls at the top of the dashboard. |
_dash.hideLinks=true | Hides dashboard links. |
refresh=30s | Auto-refreshes panels at that interval on a regular dashboard. Any duration works as long as it is not below the server's min_refresh_interval (5 s by default). Do not count on it for a shared link; use the DisplayOps auto refresh there instead. |
from=now-6h&to=now | Time range. Relative values keep the window rolling; epoch milliseconds pin it. |
timezone=America/Vancouver | Time zone for the axes, independent of the server. |
theme=dark | Force dark or light regardless of the viewing user's preference. |
orgId=1 | Organisation id. Required when the dashboard is not in the default org (the anonymous method uses a dedicated org). |
var-instance=web-01 | Sets a template variable. Not available on shared dashboard links. |
A complete wall URL for a shared dashboard: https://grafana.example.com/public-dashboards/8f2c…?kiosk&refresh=1m&from=now-24h&to=now&theme=dark. Rotate several dashboards with a Grafana playlist (/playlists/play/<uid>?kiosk) or, more simply, with a DisplayOps slideshow.
Put it on the screen and keep it there
A Raspberry Pi with a browser will show the URL once. What breaks wallboards over the following weeks is everything after that: Chromium's memory growth, a power blip, an HDMI handshake, a rebooted switch, an expired token nobody noticed. DisplayOps is a managed player for exactly this job. The steps below assume a supported Raspberry Pi and a free DisplayOps account.
Flash and pair
Write the DisplayOps image to a microSD card, plug the Pi into the TV and the network, and enter the pairing code shown on screen in the portal under DisplaysPair. Wi-Fi can be set at flash time or later from the portal.
Add the dashboard as content
Content+ New content, type Website, paste the wall URL from the method above, and save.
Then open the display, SettingsAuto refresh: one to five minutes for a shared link (the shared view has no auto-refresh of its own), or every few hours for a regular dashboard whose
refresh=parameter already keeps the panels current. The reload also clears browser memory and picks up dashboard edits.Assign it
Open the display (or a group of displays) and choose the content. The screen switches within seconds and shows the same URL again after any reboot or power cut, even before the network is back.
Schedule, if useful
Show the NOC board during the day and a calmer status page at night, or push an incident message to every screen with one click and return to the dashboards afterwards.
Watch it
The portal shows a screenshot on request, when the screen last checked in, and when the player restarted.
If a shared link is revoked or a session ends, the screenshot shows the login page and you fix the URL in one place for every screen that uses it.
Three screens are free on the Personal plan, which is enough for most NOCs and offices to start. The Grafana wallboard use case covers rotating dashboards and incident overrides in more detail.
Troubleshooting
- The login page appears after a while. A shared link was paused or revoked, the anonymous org or proxy allow-list changed, or someone logged in by hand and that session ended. Check the method, then update the content item; every screen using it updates at once.
- Panels are empty on a shared link. The dashboard uses template variables, a frontend data source or library panels. Bake the values into a copy of the dashboard and share the copy.
- "Dashboard not found" with anonymous access or JWT. The URL is missing
orgIdfor the dedicated org, or the viewer user is in the wrong org. Open the URL in a private window and read the address bar after Grafana redirects. - The menu still shows. Use
kioskwith no value.kiosk=1is ignored andkiosk=tvwas removed in newer versions. - Time picker or variables still visible in kiosk mode. Add
_dash.hideTimePicker=trueand_dash.hideVariables=true(Grafana 11.3 and newer). - The browser grows until it crashes. Set the display's Auto refresh so the page reloads periodically; DisplayOps also restarts a player that stops responding.
- Text is too small on the TV. Make a copy of the dashboard for the wall with fewer, larger panels; a 1080p TV viewed from across a room needs roughly twice the font size of a laptop screen.
Security checklist
- One dedicated org, user or link per wall, never a person's own login.
- Read-only data source credentials for anything anonymous viewers can reach.
- HTTPS everywhere; tokens in URLs are only acceptable over TLS.
- Shared links reviewed, and revoked if needed, when someone who had them leaves the team.
- Grafana behind a proxy or firewall allow-list when it is on the internet, with the auth proxy's
whitelistset. - Content URLs kept in DisplayOps, not on sticky notes; the browser on the screen never shows the address bar.
Grafana on a TV: questions we get
Does Grafana kiosk mode log in by itself?
No. Kiosk mode only hides the menus. If the dashboard needs a session, the screen shows the Grafana login page as soon as the session expires. Every method in this guide removes the need for a session on the screen.
Which method should I use on Grafana Cloud?
Shared dashboard links. Anonymous access, JWT URL login and the auth proxy are server-side settings that Grafana Cloud does not let you change.
My shared dashboard shows empty panels.
Shared dashboards do not support template variables, frontend data sources, library panels or exemplars. Replace variables with fixed values in a copy of the dashboard made for the wall, then share that copy.
Why not a JWT or a service account token in the URL?
Grafana's url_login reads the auth_token query parameter and authenticates that one request, then strips it. It does not create a session, so the dashboard page loads and every data request behind it is rejected. Service account tokens are for the HTTP API and cannot sign in the web UI at all. Both will work with header injection, which is on the DisplayOps roadmap; until then use a shared link, an anonymous org or the auth proxy, optionally behind basic auth that DisplayOps answers for you.
How do I rotate between several dashboards?
Either a Grafana playlist opened with the kiosk parameter, or a DisplayOps slideshow with one website item per dashboard and a duration for each. The slideshow is easier to change from the portal and works across different tools.
Can DisplayOps log in to Grafana for me?
Not to Grafana's own login form. DisplayOps does answer HTTP basic and digest prompts from a stored, encrypted credential, so a basic-auth route on your reverse proxy in front of an anonymous org or the auth proxy gives you a private Grafana dashboard on any screen. Header injection for service-account tokens is on the roadmap.
Related
Put the Grafana dashboard on the wall and forget about it.
Three screens free. Flash, pair, paste the URL.