A high-performance CORS (Cross-Origin Resource Sharing) module for Nginx written in Rust.
This project provides a dynamic Nginx module that handles CORS headers with advanced origin matching, preflight request handling, and upstream header merging capabilities. Built using the ngx-rust framework, it offers a modern, safe, and efficient alternative to traditional CORS solutions.
- Flexible Origin Matching: Support for exact domains, wildcard subdomains, and full wildcard patterns
- Preflight Request Handling: Automatic handling of OPTIONS requests with proper CORS headers
- Upstream Header Merging: Merge CORS headers from upstream servers
- Private Network Access: Support for Private Network Access specification
- Credential Support: Configurable credential handling for secure cross-origin requests
- Header Control: Fine-grained control over allowed and exposed headers
The project consists of two crates:
cors/: Core CORS protocol library with pure Rust implementationngx_cors_module/: Nginx module wrapper that integrates the CORS library with Nginx
# Debug build (for development)
cargo build
# Release build (optimized for production)
cargo build --releaseThe module is built as a shared library:
- Linux:
target/debug/libngx_cors_module.soortarget/release/libngx_cors_module.so - macOS:
target/debug/libngx_cors_module.dylibortarget/release/libngx_cors_module.dylib
- Build the module (see above)
- Add the load directive to your nginx.conf:
load_module /path/to/libngx_cors_module.so;- Syntax:
cors_enable on|off; - Default:
off - Context:
http,server,location
Enables or disables CORS processing for the current location.
location /api {
cors_enable on;
}- Syntax:
cors_origins <origin> ...; - Default: none
- Context:
http,server,location
Specifies allowed origins. Supports multiple matching patterns:
*: Wildcard - matches all originsexample.comor*.example.com: Matches domain and all subdomains (both http and https)api.example.com: Matches exact subdomain only (both http and https)
# Allow specific domains
cors_origins example.com api.google.com;
# Allow all subdomains
cors_origins *.example.com;
# Allow everything (development only!)
cors_origins *;Note: It is recommended to specify domains without the scheme (e.g., example.com instead of https://example.com). The module will match both http and https automatically. When a specific origin matches, the response will echo that origin back with its original scheme (e.g., Access-Control-Allow-Origin: https://example.com). Only when the wildcard * pattern matches will the response contain Access-Control-Allow-Origin: *.
- Syntax:
cors_methods <method> ...; - Default:
GET POST PUT PATCH DELETE HEAD OPTIONS - Context:
http,server,location
Specifies allowed HTTP methods for CORS requests.
cors_methods GET POST PUT DELETE;- Syntax:
cors_headers <header> ...; - Default: none
- Context:
http,server,location
Specifies allowed request headers that clients can send. These appear in the Access-Control-Allow-Headers response header.
cors_headers Content-Type Authorization X-Custom-Header;- Syntax:
cors_exposed_headers <header> ...; - Default: none
- Context:
http,server,location
Specifies response headers that browsers are allowed to access. These appear in the Access-Control-Expose-Headers response header.
cors_exposed_headers X-Total-Count X-Page-Number;- Syntax:
cors_max_age <seconds>; - Default: none
- Context:
http,server,location
Specifies how long (in seconds) preflight request results can be cached by the browser.
cors_max_age 3600; # Cache for 1 hour- Syntax:
cors_credentials on|off; - Default: none
- Context:
http,server,location
When enabled, adds Access-Control-Allow-Credentials: true to responses, allowing browsers to send credentials (cookies, authorization headers) with cross-origin requests.
cors_credentials on;Important: When credentials are enabled, you cannot use the wildcard * for origins. You must specify exact origins.
- Syntax:
cors_allow_origin_absent on|off; - Default:
off - Context:
http,server,location
Allows requests that don't include an Origin header to proceed with CORS processing.
cors_allow_origin_absent on;- Syntax:
cors_upstream_merge on|off; - Default:
off - Context:
http,server,location
When enabled, merges CORS headers from upstream responses with the module's configured headers. Useful when proxying to backends that also set CORS headers.
location /api {
cors_enable on;
cors_upstream_merge on;
proxy_pass http://backend;
}- Syntax:
cors_private_network on|off; - Default: none
- Context:
http,server,location
Enables Private Network Access support. When a preflight request includes Access-Control-Request-Private-Network: true, the response will include Access-Control-Allow-Private-Network: true.
cors_private_network on;The module processes requests in two phases:
When a request arrives with an Origin header:
-
Origin Validation: The module checks if the origin matches any configured pattern in
cors_origins- Uses specificity-based matching: exact domain > subdomain wildcard > full wildcard
- Returns the most specific matching pattern
-
Preflight Detection: If the request is an OPTIONS request with
Access-Control-Request-Methodheader, it's treated as a preflight request -
Header Generation:
- Preflight requests: Generate headers for
Access-Control-Allow-Methods,Access-Control-Allow-Headers,Access-Control-Max-Age, etc. - Regular requests: Generate headers for
Access-Control-Allow-Origin,Access-Control-Expose-Headers, etc.
- Preflight requests: Generate headers for
-
Response: Headers are added to the response and the request continues through the nginx processing chain
When cors_upstream_merge is enabled and the upstream server returns CORS headers:
- Header Collection: Collect CORS headers from the upstream response
- Merging: Merge upstream headers with module-configured headers
- Deduplication: Remove duplicate values while preserving order
http {
server {
listen 80;
server_name api.example.com;
location /api {
cors_enable on;
cors_origins app.example.com;
cors_methods GET POST PUT DELETE;
cors_headers Content-Type Authorization;
cors_exposed_headers X-Total-Count;
cors_max_age 3600;
# Your backend configuration
proxy_pass http://backend;
}
}
}location /api {
cors_enable on;
cors_origins *;
cors_methods GET POST PUT PATCH DELETE HEAD OPTIONS;
cors_headers *;
cors_credentials off; # Can't use credentials with wildcard
proxy_pass http://localhost:3000;
}location /api {
cors_enable on;
cors_origins
app1.example.com
app2.example.com
*.partner.com;
cors_methods GET POST;
cors_credentials on;
cors_max_age 7200;
proxy_pass http://backend;
}location /api {
cors_enable on;
cors_origins app.example.com;
cors_upstream_merge on; # Merge with backend CORS headers
cors_headers X-Custom-Header; # Add to backend's allowed headers
proxy_pass http://backend;
}# Run all Rust unit tests
cargo test
# Run with output
cargo test -- --nocapture
# Run specific crate tests
cargo test -p cors# Run all integration tests
cd tests/cors_module && ./run_tests.sh
# Run specific test
./run_tests.sh cors
./run_tests.sh simple_test
# Run with release build
./run_tests.sh --releaseThe module uses a three-tier specificity system for origin matching:
- Exact Domain (Highest priority):
api.example.commatches onlyhttps://api.example.comorhttp://api.example.com - Domain with Subdomains (Medium priority):
example.comor*.example.commatchesexample.com,api.example.com,foo.bar.example.com, etc. - Wildcard (Lowest priority):
*matches any origin
When multiple patterns match, the most specific one is used.
Browser Nginx (CORS Module) Backend
| | |
|-- OPTIONS /api ------------->| |
| Origin: https://app.com | |
| Access-Control-Request- | |
| Method: POST | |
| | |
| [Validate origin] |
| [Check if preflight] |
| [Generate CORS headers] |
| | |
|<-- 204 No Content ------------| |
| Access-Control-Allow- | |
| Origin: https://app.com | |
| Access-Control-Allow- | |
| Methods: GET, POST | |
| Access-Control-Max-Age: | |
| 3600 | |
Browser Nginx (CORS Module) Backend
| | |
|-- GET /api/data ------------>| |
| Origin: https://app.com | |
| | |
| [Validate origin] |
| [Add CORS headers] |
| | |
| |-- GET /data ----------->|
| | |
| |<-- 200 OK --------------|
| | {data} |
| | |
| [Merge upstream headers if enabled] |
| | |
|<-- 200 OK -------------------| |
| Access-Control-Allow- | |
| Origin: https://app.com | |
| Access-Control-Expose- | |
| Headers: X-Total-Count | |
| {data} | |
See CLAUDE.md for detailed development instructions, architecture notes, and testing guidelines.