Securing Prometheus: TLS and basic auth

Prometheus · Practical · 11 min

By default anyone who can reach port 9090 can read every metric and, with lifecycle enabled, reload or shut the server down.

Prometheus assumes a trusted network. Out of the box the API has no authentication, and the metrics it stores often reveal hostnames, versions and business numbers. Three layers to consider:

Serving side. The flag --web.config.file=web.yml enables TLS and authentication for the UI and API (same mechanism in Alertmanager and the exporters built on the exporter toolkit). tls_server_config takes cert_file and key_file (optionally client_auth_type: RequireAndVerifyClientCert with a client_ca_file for mTLS). basic_auth_users maps usernames to bcrypt hashes; generate one with htpasswd -nBC 10 "" | tr -d ':\n'. The file is re-read on every request, so password changes need no restart.

Scraping side. Per job you can set scheme: https, tls_config (ca_file, cert_file, key_file, insecure_skip_verify for dev only), basic_auth or authorization: {type: Bearer, credentials_file: ...}. Prefer the *_file variants so secrets stay out of prometheus.yml, which the Status page displays with secrets masked.

Dangerous endpoints. --web.enable-lifecycle exposes /-/reload and /-/quit; --web.enable-admin-api allows deleting series and taking snapshots. Both are off by default; keep them off unless the server is behind auth.

What Prometheus does not have is authorization: any authenticated user can run any query, read the config and see all targets. Per-team or per-tenant access control means a reverse proxy, or a multi-tenant backend such as Mimir or Thanos with its own tenancy model. Remote write endpoints should likewise sit behind TLS and a credential, since anyone who can write can poison your data.

Example

# web.yml  (prometheus --web.config.file=web.yml)
tls_server_config:
  cert_file: /etc/prometheus/tls/server.crt
  key_file: /etc/prometheus/tls/server.key
basic_auth_users:
  # htpasswd -nBC 10 "" | tr -d ':\n'   -> paste the bcrypt hash
  grafana: $2y$10$3ZkE9bQx1t5tJ7mXl0u9IuK0M9QW0qVb0CGz2nL9Vn6wYd6wL1Y3e

# prometheus.yml: scrape an exporter protected the same way
scrape_configs:
  - job_name: node
    scheme: https
    tls_config:
      ca_file: /etc/prometheus/tls/ca.crt
    basic_auth:
      username: prometheus
      password_file: /etc/prometheus/secrets/node_exporter.pw
    static_configs: [{targets: ["web1:9100"]}]

# Check it: promtool check web-config web.yml
# curl -u grafana:... --cacert ca.crt https://prom.example.com:9090/api/v1/query?query=up

Remember

  • --web.config.file gives TLS and basic auth to the UI/API
  • Scrape jobs set scheme, tls_config and credential files
  • No authorization exists; use a proxy or a multi-tenant backend
  • Keep lifecycle and admin APIs disabled unless protected

Watch out for

  • Setting insecure_skip_verify: true in production 'temporarily' and never removing it.

Check yourself

  1. Team A must only see its own namespace's metrics in a shared Prometheus. What does Prometheus offer?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: prometheus.io/docs/prometheus/latest/configuration/https/