Skip to main content
Query Exporter handles sensitive information like database credentials and connects to critical infrastructure. This guide covers security best practices for production deployments.

SSL/TLS Configuration

Query Exporter supports HTTPS for the metrics endpoint, encrypting communication between Prometheus and the exporter.

Enabling HTTPS

Via command-line options:
query-exporter \
  --ssl-private-key /path/to/private.key \
  --ssl-public-key /path/to/public.crt \
  --ssl-ca /path/to/ca.crt
Via environment variables:
export QE_SSL_PRIVATE_KEY=/path/to/private.key
export QE_SSL_PUBLIC_KEY=/path/to/public.crt
export QE_SSL_CA=/path/to/ca.crt

query-exporter --config config.yaml
Via .env file:
# .env
QE_SSL_PRIVATE_KEY=/path/to/private.key
QE_SSL_PUBLIC_KEY=/path/to/public.crt
QE_SSL_CA=/path/to/ca.crt

SSL Options

OptionEnvironment VariableDescription
--ssl-private-keyQE_SSL_PRIVATE_KEYFull path to SSL private key file
--ssl-public-keyQE_SSL_PUBLIC_KEYFull path to SSL public key/certificate file
--ssl-caQE_SSL_CAFull path to SSL Certificate Authority file

Certificate Requirements

  • Private key: PEM format, unencrypted
  • Public key: PEM format certificate
  • CA certificate: PEM format (optional, for client authentication)

Example with Self-Signed Certificate

Generate certificates:
# Generate private key
openssl genrsa -out private.key 2048

# Generate self-signed certificate
openssl req -new -x509 -key private.key -out public.crt -days 365
Configure Query Exporter:
query-exporter \
  --ssl-private-key ./private.key \
  --ssl-public-key ./public.crt
Scrape with Prometheus:
# prometheus.yml
scrape_configs:
  - job_name: 'query-exporter'
    scheme: https
    tls_config:
      insecure_skip_verify: true  # Only for self-signed certs
    static_configs:
      - targets: ['localhost:9560']
Never use self-signed certificates in production without proper validation. Use certificates from a trusted CA.

Credential Management

Never hardcode database credentials in configuration files. Use environment variables and secure secret management.

Using Environment Variables

Recommended approach with !env tag:
# config.yaml
databases:
  production_db:
    dsn: !env DATABASE_DSN
Set in environment:
export DATABASE_DSN="postgresql://user:password@host:5432/db"

Using .env Files

For local development and Docker deployments: Create .env file:
# .env (DO NOT commit to version control)
DATABASE_DSN=postgresql://dbuser:[email protected]:5432/proddb
READONLY_DSN=postgresql://readonly:[email protected]:5432/proddb
Configure .gitignore:
# .gitignore
.env
.env.local
*.key
*.crt
secrets/
Load automatically:
# Query Exporter automatically loads .env from current directory
query-exporter --config config.yaml
Custom .env location:
export QE_DOTENV=/opt/query-exporter/.env
query-exporter --config config.yaml

Partial DSN with Environment Variables

Break down credentials into separate variables:
# config.yaml
databases:
  app_db:
    dsn:
      dialect: postgresql
      user: !env DB_USER
      password: !env DB_PASSWORD
      host: !env DB_HOST
      port: !env DB_PORT
      database: !env DB_NAME
      options:
        sslmode: require
# .env
DB_USER=myapp
DB_PASSWORD=super-secret-password
DB_HOST=db.example.com
DB_PORT=5432
DB_NAME=production

Docker Secrets

For Docker Swarm deployments:
# docker-compose.yml
version: '3.8'
services:
  query-exporter:
    image: adonato/query-exporter:latest
    secrets:
      - db_password
    environment:
      DATABASE_DSN: postgresql://user:$(cat /run/secrets/db_password)@host/db
    volumes:
      - ./config.yaml:/config/config.yaml

secrets:
  db_password:
    external: true

Kubernetes Secrets

For Kubernetes deployments:
# secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: query-exporter-secrets
type: Opaque
stringData:
  database-dsn: postgresql://user:password@host:5432/db
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: query-exporter
spec:
  template:
    spec:
      containers:
      - name: query-exporter
        image: adonato/query-exporter:latest
        env:
        - name: DATABASE_DSN
          valueFrom:
            secretKeyRef:
              name: query-exporter-secrets
              key: database-dsn

Database Connection Security

Use Read-Only Accounts

Create dedicated read-only database users for Query Exporter: PostgreSQL:
-- Create read-only user
CREATE ROLE query_exporter_ro WITH LOGIN PASSWORD 'secure-password';

-- Grant connection
GRANT CONNECT ON DATABASE mydb TO query_exporter_ro;

-- Grant read-only access
GRANT USAGE ON SCHEMA public TO query_exporter_ro;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO query_exporter_ro;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO query_exporter_ro;
MySQL:
-- Create read-only user
CREATE USER 'query_exporter_ro'@'%' IDENTIFIED BY 'secure-password';

-- Grant read-only access
GRANT SELECT ON mydb.* TO 'query_exporter_ro'@'%';
FLUSH PRIVILEGES;
SQL Server:
-- Create read-only user
CREATE LOGIN query_exporter_ro WITH PASSWORD = 'secure-password';
USE mydb;
CREATE USER query_exporter_ro FOR LOGIN query_exporter_ro;
EXEC sp_addrolemember 'db_datareader', 'query_exporter_ro';

SSL/TLS for Database Connections

Always use encrypted connections to databases: PostgreSQL with SSL:
databases:
  secure_db:
    dsn: postgresql://user:pass@host:5432/db?sslmode=require
SSL modes for PostgreSQL:
  • disable - No SSL (not recommended)
  • require - SSL required, no certificate verification
  • verify-ca - SSL with CA verification
  • verify-full - SSL with full certificate verification
MySQL with SSL:
databases:
  secure_db:
    dsn: mysql://user:pass@host:3306/db?ssl_mode=REQUIRED
SQL Server with encryption:
databases:
  secure_db:
    dsn: mssql+pymssql://user:pass@host:1433/db?encrypt=true

Network Security

Restrict database access:
# PostgreSQL pg_hba.conf
# Allow only from Query Exporter host
host    mydb    query_exporter_ro    10.0.1.50/32    md5
Use firewall rules:
# iptables - allow only from specific IP
iptables -A INPUT -p tcp --dport 5432 -s 10.0.1.50 -j ACCEPT
iptables -A INPUT -p tcp --dport 5432 -j DROP

Metrics Endpoint Security

Bind to Specific Interface

Don’t expose metrics publicly:
# Bind to localhost only
query-exporter --host 127.0.0.1 --port 9560

# Or use environment variable
export QE_HOST=127.0.0.1
In Docker:
# docker-compose.yml
services:
  query-exporter:
    image: adonato/query-exporter:latest
    ports:
      - "127.0.0.1:9560:9560"  # Only accessible from host

Reverse Proxy with Authentication

Use Nginx or similar to add authentication:
# nginx.conf
server {
    listen 443 ssl;
    server_name metrics.example.com;

    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    location /metrics {
        auth_basic "Metrics Access";
        auth_basic_user_file /etc/nginx/.htpasswd;
        proxy_pass http://localhost:9560;
    }
}

Prometheus Authentication

Configure Prometheus to use basic auth:
# prometheus.yml
scrape_configs:
  - job_name: 'query-exporter'
    scheme: https
    basic_auth:
      username: prometheus
      password: secret
    static_configs:
      - targets: ['metrics.example.com']

Logging Security

Avoid Logging Sensitive Data

Query Exporter logs don’t include credentials by default, but be careful with query parameters: Configure log level:
# Use 'info' or 'warning' in production, not 'debug'
query-exporter --log-level info
JSON logging for log aggregation:
query-exporter --log-format json

Secure Log Storage

Docker logging:
# docker-compose.yml
services:
  query-exporter:
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

File Permissions

Protect configuration files and SSL certificates:
# Configuration files
chmod 600 config.yaml
chmod 600 .env

# SSL certificates
chmod 600 private.key
chmod 644 public.crt

# Ensure correct ownership
chown query-exporter:query-exporter config.yaml .env
chown query-exporter:query-exporter private.key public.crt

Docker Security

Run as Non-Root User

# Dockerfile
FROM adonato/query-exporter:latest

USER nobody:nogroup

Read-Only Filesystem

# docker-compose.yml
services:
  query-exporter:
    image: adonato/query-exporter:latest
    read_only: true
    security_opt:
      - no-new-privileges:true

Secrets Management

# docker-compose.yml
services:
  query-exporter:
    image: adonato/query-exporter:latest
    secrets:
      - source: config
        target: /config/config.yaml
        mode: 0400
      - source: dotenv
        target: /config/.env
        mode: 0400

secrets:
  config:
    file: ./config.yaml
  dotenv:
    file: ./.env

Security Checklist

1

Credentials

  • Never commit credentials to version control
  • Use environment variables or secret management
  • Add .env to .gitignore
  • Rotate credentials regularly
2

Database Access

  • Create dedicated read-only database users
  • Use SSL/TLS for database connections
  • Restrict network access to databases
  • Enable database connection encryption
3

Exporter Access

  • Enable HTTPS for metrics endpoint
  • Bind to specific network interfaces
  • Use reverse proxy with authentication
  • Configure Prometheus with TLS
4

Deployment

  • Set appropriate file permissions (600 for configs, keys)
  • Run as non-root user in containers
  • Use read-only filesystem where possible
  • Configure log levels appropriately
5

Monitoring

  • Monitor database_errors metric
  • Alert on authentication failures
  • Review logs regularly
  • Test configurations with --check-only

Example Secure Configuration

Here’s a complete secure configuration:
# config.yaml
databases:
  postgres_prod:
    dsn: !env POSTGRES_PROD_DSN
    connection-pool:
      size: 2
      max-overflow: 0
  
  postgres_replica:
    dsn: !env POSTGRES_REPLICA_DSN
    connection-pool:
      size: 2
      max-overflow: 0

metrics:
  query_success:
    type: counter
    description: Successful query executions
    labels: [query_name]

queries:
  health_check:
    interval: 30
    timeout: 5
    databases: [postgres_replica]
    metrics: [query_success]
    sql: SELECT 1 as query_success, 'health_check' as query_name
# .env (never commit this)
POSTGRES_PROD_DSN=postgresql://readonly:[email protected]:5432/app?sslmode=verify-full
POSTGRES_REPLICA_DSN=postgresql://readonly:[email protected]:5432/app?sslmode=verify-full
# Start with SSL and restrictive binding
query-exporter \
  --config config.yaml \
  --host 127.0.0.1 \
  --log-level info \
  --log-format json \
  --ssl-private-key /etc/ssl/private/exporter.key \
  --ssl-public-key /etc/ssl/certs/exporter.crt

Further Reading

Build docs developers (and LLMs) love