caddyserver/caddy · error

opening log writer using %#v: %v

Error message

opening log writer using %#v: %v

What it means

Thrown during CustomLog/BaseLog provisioning when the configured log writer module cannot be opened. Caddy loads the writer module (WriterRaw, e.g. file_writer, net_writer), then calls logging.openWriter; any failure from the writer's own open logic (file permissions, bad path, unreachable socket) surfaces wrapped here with the writer value printed via %#v.

Source

Thrown at logging.go:356

	levelEnabler zapcore.LevelEnabler
	core         zapcore.Core
}

func (cl *BaseLog) provisionCommon(ctx Context, logging *Logging) error {
	if cl.WriterRaw != nil {
		mod, err := ctx.LoadModule(cl, "WriterRaw")
		if err != nil {
			return fmt.Errorf("loading log writer module: %v", err)
		}
		cl.writerOpener = mod.(WriterOpener)
	}
	if cl.writerOpener == nil {
		cl.writerOpener = StderrWriter{}
	}
	var err error
	cl.writer, _, err = logging.openWriter(cl.writerOpener)
	if err != nil {
		return fmt.Errorf("opening log writer using %#v: %v", cl.writerOpener, err)
	}

	// set up the log level
	cl.levelEnabler, err = parseLevel(cl.Level)
	if err != nil {
		return err
	}

	if cl.EncoderRaw != nil {
		mod, err := ctx.LoadModule(cl, "EncoderRaw")
		if err != nil {
			return fmt.Errorf("loading log encoder module: %v", err)
		}
		cl.encoder = mod.(zapcore.Encoder)

		// if the encoder module needs the writer to determine
		// the correct default to use for a nested encoder, we
		// pass it down as a secondary provisioning step

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Check the wrapped error text (%v) — it names the concrete cause (permission denied, no such file or directory, connection refused).
  2. Verify the log writer path is writable by the Caddy process user (touch the file as that user; fix ownership or chmod).
  3. Create missing parent directories or use an absolute path that exists.
  4. For net_writer, confirm the remote endpoint is listening and reachable from the Caddy host.
  5. Temporarily remove the writer block to fall back to the default StderrWriter{} and confirm the rest of the config loads.

Example fix

// before (caddyfile)
log {
    output file /var/log/caddy/access.log
}

// after — directory exists and is writable, or use a writable path
log {
    output file /home/caddy/logs/access.log
}

# shell: mkdir -p /var/log/caddy && chown caddy:caddy /var/log/caddy
Defensive patterns

Strategy: validation

Validate before calling

// before loading config, verify writability of file log outputs
import "os"

func checkLogWritable(path string) error {
    f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
    if err != nil {
        return fmt.Errorf("log path %s not writable: %w", path, err)
    }
    return f.Close()
}

Try / catch

// Go: treat config-load errors as fatal, but log the wrapped cause
if err := caddy.Load(cfg, true); err != nil {
    if strings.Contains(err.Error(), "opening log writer") {
    log.Printf("log writer unavailable: %v", err)
    }
    return err
}

Prevention

When it happens

Trigger: Provisioning a logs config whose writer fails to open: file_writer with an unwritable/read-only path or missing directory, net_writer with an unreachable address, or a custom WriterOpener whose OpenWriter returns an error.

Common situations: Running Caddy in a container as a non-root user while logs point at /var/log with root ownership; a path with a typo'd or non-existent parent directory; systemd unit with restrictive ReadWritePaths; switching log output to a socket that is not yet up.

Related errors


AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15). Data as JSON: /api/errors/d0646f5467369a81. Report an issue: GitHub.