Mastering Caddy Logging: A Complete Guide to Access, Error, and Structured Logs

 

Mastering Logging in Caddy

Caddy is a modern, lightweight, and secure web server that’s rapidly gaining popularity thanks to its simplicity, automatic HTTPS, and powerful configuration options. One of the most important aspects of managing a Caddy-powered application is logging. Proper logging ensures you have visibility into your system, can debug issues efficiently, and monitor performance in real-time.

In this article, we’ll break down how logging works in Caddy, why it matters, and how you can configure it for your own projects.


What is Logging in Caddy?

Logging in Caddy refers to the process of capturing details about requests, errors, and internal server behavior. With logs, you can:

  • Track incoming requests and responses
  • Debug configuration or runtime issues
  • Monitor server performance and health
  • Meet compliance and audit requirements

Caddy uses a structured logging system, making logs easier to parse and integrate with external monitoring tools like Better Stack, Loki, or ELK.


Default Logging Behavior

By default, Caddy provides access logs and error logs:

  • Access logs record details about every incoming HTTP request, including method, path, status code, response time, and client IP.
  • Error logs capture warnings, misconfigurations, or unexpected runtime issues.

Logs are output in JSON format, which makes them machine-readable and easy to analyze with external log management systems.


Configuring Logging in Caddy

Caddy allows highly flexible logging configuration via the Caddyfile or JSON configuration.

Example: Basic Logging in Caddyfile

:80 {
    log {
        output file /var/log/caddy/access.log
        format single_field common_log
        level INFO
    }
}

This configuration:

  • Saves logs to /var/log/caddy/access.log
  • Uses the common log format for readability
  • Sets the logging level to INFO

Logging Levels

Caddy supports different logging levels to control verbosity:

  • DEBUG – detailed debugging information
  • INFO – general operational events (default)
  • WARN – potentially harmful situations
  • ERROR – serious issues that need attention

Advanced Logging Example

{
    logging {
        level DEBUG
        logs {
            default {
                level INFO
                format json
                output stdout
            }
            errors {
                level ERROR
                output file /var/log/caddy/errors.log
            }
        }
    }
}

Here we:

  • Enable global debug logging
  • Output structured JSON logs for default events
  • Send errors to a dedicated file for easier analysis

Sending Logs to External Systems

Caddy logs can be streamed to external monitoring platforms. Common integrations include:

  • Better Stack (via HTTP ingestion)
  • Grafana Loki
  • Elastic Stack (ELK)
  • Syslog servers

For example, sending logs via stdout lets you capture them directly in Docker or Kubernetes environments, where they can be centralized by the platform.


Best Practices for Caddy Logging

  1. Use JSON format for better parsing and searchability.
  2. Separate access and error logs for easier debugging.
  3. Rotate logs using tools like logrotate or your container orchestration system.
  4. Forward logs to a central monitoring platform for long-term storage and alerting.
  5. Adjust log levels per environment – use DEBUG in development but stick to INFO or higher in production.

Example: Caddyfile

{
    # valid options if you need them:
    # auto_https off | disable_redirects | disable_certs | ignore_loaded_certs
    email {$EMAIL}
}

# --- Security header for static and media ---
(security_headers) {
    header {
        X-Content-Type-Options "nosniff"
        X-Frame-Options "DENY"
        Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
    }
}

{$DOMAIN} {
    encode gzip zstd

    log {
        output file /var/log/caddy/access.log {
            roll_size 100mb
            roll_keep 10
            roll_keep_for 720h
        }
        format json
    }

    @probes {
        path /.env
        path /.git/*
        path /.vscode/*
        path /.DS_Store
        path /info.php
        path /server-status
        path /actuator/*
        path /config.json
        path /@vite/env
    }
    # Block access to sensitive files
    handle @probes {
        respond "Not found" 404
    }

    # Lightweight, non-overlapping headers from edge
    header {
        Referrer-Policy "strict-origin-when-cross-origin"
        Permissions-Policy "geolocation=(), microphone=(), camera=()"
        # NOTE: HSTS/X-Frame-Options/X-Content-Type-Options are set by Django.
    }

    @health path /healthz
    handle @health {
        respond "ok" 200
    }

    handle_path /media/* {
        root * /var/www/bloemie/media
        file_server

        header Cache-Control "public, max-age=86400"
        header X-Robots-Tag "noindex, nofollow"
        # Security headers for static only
        import security_headers
    }

    handle_path /static/* {
        root * /var/www/bloemie/static
        file_server
        # With Django ManifestStaticFilesStorage, filenames are fingerprinted → immutable
        header Cache-Control "public, max-age=31536000, immutable"
        header X-Robots-Tag "noindex, nofollow"
        # Security headers for static only
        import security_headers
    }

    reverse_proxy web:8000 {
        header_up Host {host}
        flush_interval -1
    }
}
Conclusion

Logging is essential for maintaining, debugging, and scaling your Caddy server. By leveraging Caddy’s flexible configuration, you can tailor logs to your environment, integrate with modern observability tools, and gain full visibility into your applications.

If you’re just starting out, begin with simple access and error logs, then expand into structured JSON logging and integrations with platforms like Better Stack as your infrastructure grows.


Source:

  • https://betterstack.com/community/guides/logging/caddy-logging/

Comments

Popular posts from this blog

Highlights from the 2025 Stack Overflow Developer Survey

psql: error: connection to server at "localhost" (127.0.0.1), port 5433 failed: ERROR: failed to authenticate with backend using SCRAM DETAIL: valid password not found