Access-Control-Allow-Origin: * allows any website to access your resources. Always specify exact origins in production.
For production use with a single trusted origin:
sitename.com {
# Secure configuration for single trusted origin
file_server
encode zstd gzip
# Handle preflight requests
@cors_preflight {
method OPTIONS
}
header @cors_preflight {
Access-Control-Allow-Origin "https://example.com"
Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
Access-Control-Allow-Headers "Content-Type, Authorization"
Access-Control-Max-Age "86400"
Vary "Origin"
}
respond @cors_preflight 204
# For actual requests
header {
Access-Control-Allow-Origin "https://example.com"
Vary "Origin"
}
}
To support multiple specific origins, use regex matching with Caddy's matcher system:
sitename.com {
# Validate multiple origins using regex
@cors_origin_match {
header_regexp origin Origin ^https?://(www\.)?(example\.com|app\.example\.com)$
}
# Preflight for matched origins
@cors_preflight {
method OPTIONS
}
header @cors_preflight {
Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
Access-Control-Allow-Headers "Content-Type, Authorization"
Access-Control-Max-Age "86400"
}
header @cors_origin_match {
Access-Control-Allow-Origin "{http.request.header.Origin}"
Vary "Origin"
}
respond @cors_preflight 204
# Your backend
reverse_proxy localhost:8080
}
For authenticated APIs that require cookies or authentication headers:
api.example.com {
# CORS with credentials for authenticated API
@cors_allowed {
header Origin https://example.com
}
@cors_preflight {
method OPTIONS
}
header @cors_preflight {
Access-Control-Allow-Origin "https://example.com"
Access-Control-Allow-Methods "GET, POST, PUT, DELETE"
Access-Control-Allow-Headers "Content-Type, Authorization"
Access-Control-Allow-Credentials "true"
Access-Control-Max-Age "86400"
Vary "Origin"
}
respond @cors_preflight 204
header @cors_allowed {
Access-Control-Allow-Origin "https://example.com"
Access-Control-Allow-Credentials "true"
Vary "Origin"
}
# API backend
reverse_proxy localhost:3000
}
Apply different CORS policies to different paths:
example.com {
# Public API - open CORS
handle /api/public/* {
header {
Access-Control-Allow-Origin "*"
}
reverse_proxy localhost:8080
}
# Private API - restricted CORS
handle /api/private/* {
@cors_origin {
header Origin https://example.com
}
header @cors_origin {
Access-Control-Allow-Origin "https://example.com"
Access-Control-Allow-Credentials "true"
Vary "Origin"
}
reverse_proxy localhost:8080
}
# Static files - no CORS
handle {
file_server
}
}
Only use this for completely public APIs where you want to allow access from any origin:
sitename.com {
file_server
encode zstd gzip
header {
Access-Control-Allow-Headers *
Access-Control-Allow-Methods *
Access-Control-Allow-Origin *
}
@options {
method OPTIONS
}
respond @options 204
}
Warning: This configuration allows unrestricted access from any website. It provides no origin validation and should only be used for truly public data.
Vary: Origin when the response varies by origin to prevent caching issuesAccess-Control-Max-Age to cache preflight responses and reduce overhead*) for origin when Access-Control-Allow-Credentials is trueAccess-Control-Allow-Origin - for multiple origins, validate and echo the request originheader directive - there is no built-in cors middlewareInstructions for Caddy Version 1 (older version) can be found here.
For comprehensive testing instructions including curl commands, browser DevTools usage, and troubleshooting common CORS errors, see the CORS Testing Guide.
The content on this site stays fresh thanks to help from users like you! If you have suggestions or would like to contribute, fork us on GitHub.
Save 39% on CORS in Action with promotional code hossainco at manning.com/hossain