John Siu Blog

Tech - Business Tool, Personal Toys

Caddy 2 Caddyfile Usage

☰ Table of Content

Some Caddy v2 Caddyfile examples.

Global Options

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
  # Turn on all debug log
  debug
  # Setup default sni
  default_sni example.com
  # Turn off admin port
  admin off
  # Turn on http3
  experimental_http3
}

Multiple Domains

1
2
3
www.example.com test.example.com {
  ...
}
1
2
3
old.example.com other.example.com {
  redir https://example.com{uri}
}

Error Handling

1
2
3
4
5
6
7
8
www.example.com {
  root * /www/example.com
  file_server
  handle_errors {
    rewrite * /{http.error.status_code}.html
    file_server
  }
}

Template

Create your own myip page.

/www/example.com/myip/index.html

1
{{.RemoteIP}}

Caddy will parse all files:

1
2
3
4
5
www.example.com {
  root * /www/example.com
  file_server
  templates
}

Caddy will parse files in /www/example.com/myip/:

1
2
3
4
5
www.example.com {
  root * /www/example.com
  file_server
  templates /www/example.com/myip/
}

Caddy will only parse /www/example.com/myip/index.html:

1
2
3
4
5
www.example.com {
  root * /www/example.com
  file_server
  templates /www/example.com/myip/index.html
}
1
2
3
4
5
6
7
www.example.com {
  root * /www/example.com
  file_server
  header Access-Control-Allow-Origin *
  header Cache-Control max-age=3600
  header /css/* Cache-Control max-age=604800
}

Snippet / Import

Caddy v2.1+ allow common section to be factored out (snippet) and re-used by different sections.

Cache-Control

1
2
3
4
5
(cache_ctl) {
  header /css/* Cache-Control max-age=3600
  header /img/* Cache-Control max-age=3600
  header /js/* Cache-Control max-age=3600
}

Force Https ACME

1
2
3
4
5
6
7
(acme_https) {
	tls {
		issuer acme {
			disable_http_challenge
		}
	}
}

Log

Log to standard out:

1
2
3
4
5
6
(log_stdout) {
  log {
    format json
    output stdout
  }
}

Auto name log file base on hostname:

1
2
3
4
5
6
7
8
(log_file) {
  log {
    format logfmt
    output file /var/log/caddy/{host}.access.log {
      roll_keep 7
    }
  }
}

Log setting is per site. See site option below.

Redirect

Caddy < 2.2

1
2
3
4
5
6
7
8
(_redir) {
	@{args.0} {
		not file /{path} /{path}/ /{path}index.html /{path}/index.html
		not path_regexp r {args.1}/.*
		path_regexp r ^{args.0}/?(?P<goto>.*)$
	}
	redir @{args.0} {args.1}/{re.r.goto}
}

Caddy >= 2.2

1
2
3
4
5
6
7
8
(_redir) {
	@{args.0} {
		not file /{path} /{path}/
		not path_regexp r {args.1}/.*
		path_regexp r ^{args.0}/?(?P<goto>.*)$
	}
	redir @{args.0} {args.1}/{re.r.goto}
}

Usage:

1
2
3
4
5
int.jsiu.dev {
  # import _redir <from> <to>
  import _redir /\d{4}/\d{2}(/\d{2})? /blog
  import _redir /category /tags
}

<from> and <to> should always start with /, but no trailing /.

Site Option

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
(site_option) {
  encode zstd gzip
  file_server
  handle_errors {
    rewrite * /{http.error.status_code}.html
    file_server
  }
  import acme_https
  import log_file
  root * /www/{host}
}

Use above in site sections:

1
2
3
4
5
6
7
8
9
example1.com {
  import cache_ctl
  import site_option
}

example2.com {
  import cache_ctl
  import site_option
}

Multiple domains:

1
2
3
4
example1.com example2.com {
  import cache_ctl
  import site_option
}

My IP

This snippet allow creation of a text only “MY IP” page.

1
2
3
4
5
(myip) {
	header {args.0} Content-Type text/plain
	respond {args.0} "{{.RemoteIP}}"
	templates {args.0}
}

Following site use the snippet to create a “MY IP” page at /myip and /myip/.

1
2
3
4
5
example.com {
  import myip /myip
  import myip /myip/
  import site_option
}

If you want to use a sub-domain for this purpose, then one line is enough.

1
2
3
myip.example.com {
  import myip /
}

Favicon

Provide a favicon.ico to any site:

1
2
3
4
5
(favicon) {
	file_server /favicon.ico {
		root {args.0}
	}
}

Use as follow:

1
2
3
4
example.com {
  import favicon /var/www/static
  import site_option
}

Also check out example in CORS.

CORS

Single

1
2
3
4
5
6
api.example.com {
  @site2 header Origin https://w3.example.com
  header @site2 Access-Control-Allow-Origin https://w3.example.com
  file_server
  root * /www/api.example.com
}

Multiple

Caddy v2.1+

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
(cors) {
  @{args.0} header Origin {args.0}
  header @{args.0} Access-Control-Allow-Origin {args.0}
}

api.example.com {
  root * /www/api.example.com
  file_server

  import cors https://example.com
  import cors https://www.example.com
  import cors https://another.example.com
}

Use regular expression to cover domain and sub-domains.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
(cors_reg) {
  @{args.0} header_regexp Origin {args.0}
  header @{args.0} Access-Control-Allow-Origin {http.request.header.origin}
}

api.example.com {
  root * /www/api.example.com
  file_server

  import cors_reg https://([[:alnum:]-]+\.)*example.com
}

Reverse-Proxy

When hosting service behind reverse-proxy, some service by default set Access-Control-Allow-Origin to *. To change that:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
api.example.com {
  import cors https://example.com
  import cors https://www.example.com
  import cors https://another.example.com

  reverse_proxy http://backend.example.com {
    # Remove Access-Control-Allow-Origin from backend response
    header_down -Access-Control-Allow-Origin
  }
}

With cors_reg:

1
2
3
4
5
6
7
8
api.example.com {
  import cors_reg https://([[:alnum:]-]+\.)*example.com

  reverse_proxy http://backend.example.com {
    # Remove Access-Control-Allow-Origin from backend response
    header_down -Access-Control-Allow-Origin
  }
}

John Siu

Update: 2021-03-29
comments powered by Disqus