traefik_conditional_headers

package module
v0.0.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 7, 2026 License: BSD-3-Clause Imports: 3 Imported by: 0

README

Conditional Headers Plugin for Traefik

valksor BSD-3-Clause Coverage Status Go Report Card

A Traefik middleware plugin that sets HTTP request headers conditionally based on the incoming hostname. Supports multiple hosts sharing the same header configuration with flexible matching strategies.

Features

  • Multiple Hosts per Rule: Configure several hosts to share the same headers
  • Flexible Matching: Exact host matching, wildcard subdomain matching, and partial matching
  • Rule Priority: First matching rule wins for predictable behavior
  • Port Handling: Automatically strips port numbers from incoming requests
  • Performance Optimized: Efficient rule processing with configurable order
  • Yaegi Compatible: Works with Traefik's plugin interpreter for configuration testing

Installation

This plugin is available from the Traefik Plugin Catalog and can be used without Pilot.

Using Plugin Catalog

YAML Configuration:

# traefik.yml
experimental:
  plugins:
    conditional-headers:
      moduleName: github.com/valksor/traefik-conditional-headers
      version: "v0.0.3"

CLI Arguments:

traefik --experimental.plugins.conditional-headers.moduleName=github.com/valksor/traefik-conditional-headers \
        --experimental.plugins.conditional-headers.version=v0.0.3
Docker Compose
version: '3.8'
services:
  traefik:
    image: traefik:v3.5
    command:
      - "--experimental.plugins.conditional-headers.moduleName=github.com/valksor/traefik-conditional-headers"
      - "--experimental.plugins.conditional-headers.version=v0.0.3"
    volumes:
      - ./traefik.yml:/traefik.yml
    ports:
      - "80:80"
      - "443:443"
Kubernetes Installation

Using Helm Values:

# values.yaml
additionalArguments:
  - "--experimental.plugins.conditional-headers.moduleName=github.com/valksor/traefik-conditional-headers"
  - "--experimental.plugins.conditional-headers.version=v0.0.3"

Using ConfigMap:

# traefik-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: traefik-config
data:
  traefik.yml: |
    experimental:
      plugins:
        conditional-headers:
          moduleName: github.com/valksor/traefik-conditional-headers
          version: "v0.0.3"
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: traefik
spec:
  template:
    spec:
      containers:
        - name: traefik
          args:
            - "--experimental.plugins.conditional-headers.moduleName=github.com/valksor/traefik-conditional-headers"
            - "--experimental.plugins.conditional-headers.version=v0.0.3"
Development Setup

For developers who want to contribute or modify the plugin:

Clone and Test
# Clone the repository
git clone https://github.com/valksor/traefik-conditional-headers.git
cd traefik-conditional-headers

# Run tests
go test -v ./...

# Run the full quality gate + tests + coverage (default target)
make

# Non-mutating lint only
make lint

Requires golangci-lint v2.12.2 and gawk; run make tools to install goimports/gofumpt. See Contributing → Development Setup for details. make/make quality reformat files in place.

Local Development

Use the source directory directly - no building required:

YAML Configuration for Local Testing:

# traefik.yml
experimental:
  localPlugins:
    conditional-headers:
      moduleName: github.com/valksor/traefik-conditional-headers

CLI Arguments for Local Testing:

traefik --experimental.localPlugins.conditional-headers.moduleName=github.com/valksor/traefik-conditional-headers

Docker with Local Plugin:

version: '3.8'
services:
  traefik:
    image: traefik:v3.5
    command:
      - "--experimental.localPlugins.conditional-headers.moduleName=github.com/valksor/traefik-conditional-headers"
    volumes:
      - ./traefik.yml:/traefik.yml
      - ./traefik-conditional-headers:/plugins-local/src/github.com/valksor/traefik-conditional-headers
    ports:
      - "80:80"
      - "443:443"

Configuration

Quick Start Example

YAML Configuration:

http:
  middlewares:
    my-conditional-headers:
      plugin:
        conditional-headers:
          rules:
            - hosts:
                - api.example.com
                - "*.dev.example.com"
              headers:
                X-Environment: "development"
                X-Service: "api-gateway"

Docker Compose Labels:

labels:
  - "traefik.http.middlewares.my-conditional-headers.plugin.conditional-headers.rules[0].hosts[0]=api.example.com"
  - "traefik.http.middlewares.my-conditional-headers.plugin.conditional-headers.rules[0].hosts[1]=*.dev.example.com"
  - "traefik.http.middlewares.my-conditional-headers.plugin.conditional-headers.rules[0].headers.X-Environment=development"
  - "traefik.http.middlewares.my-conditional-headers.plugin.conditional-headers.rules[0].headers.X-Service=api-gateway"
Multiple Hosts with Same Headers

YAML Configuration:

http:
  middlewares:
    my-conditional-headers:
      plugin:
        conditional-headers:
          rules:
            - hosts:
                - demo.dev.io
                - test.demo.dev.io
                - staging.demo.dev.io
              headers:
                X-App-Kernel-Name: demo.valksor.com
                X-Environment: production
            - hosts:
                - prod.example.com
              headers:
                X-App-Kernel-Name: prod.example.com
                X-Environment: production

CLI Equivalent:

traefik \
  --middlewares.my-conditional-headers.plugin.conditional-headers.rules[0].hosts[0]=demo.dev.io \
  --middlewares.my-conditional-headers.plugin.conditional-headers.rules[0].hosts[1]=test.demo.dev.io \
  --middlewares.my-conditional-headers.plugin.conditional-headers.rules[0].hosts[2]=staging.demo.dev.io \
  --middlewares.my-conditional-headers.plugin.conditional-headers.rules[0].headers.X-App-Kernel-Name=demo.valksor.com \
  --middlewares.my-conditional-headers.plugin.conditional-headers.rules[0].headers.X-Environment=production \
  --middlewares.my-conditional-headers.plugin.conditional-headers.rules[1].hosts[0]=prod.example.com \
  --middlewares.my-conditional-headers.plugin.conditional-headers.rules[1].headers.X-App-Kernel-Name=prod.example.com \
  --middlewares.my-conditional-headers.plugin.conditional-headers.rules[1].headers.X-Environment=production
Wildcard Subdomain Matching

YAML Configuration:

http:
  middlewares:
    my-conditional-headers:
      plugin:
        conditional-headers:
          rules:
            - hosts:
                - "*.internal.dev.io"
              headers:
                X-App-Kernel-Name: internal.valksor.com
                X-Network: "internal"
            - hosts:
                - "*.public.dev.io"
              headers:
                X-App-Kernel-Name: public.valksor.com
                X-Network: "public"

Docker Compose Labels:

labels:
  - "traefik.http.middlewares.my-conditional-headers.plugin.conditional-headers.rules[0].hosts[0]=*.internal.dev.io"
  - "traefik.http.middlewares.my-conditional-headers.plugin.conditional-headers.rules[0].headers.X-App-Kernel-Name=internal.valksor.com"
  - "traefik.http.middlewares.my-conditional-headers.plugin.conditional-headers.rules[0].headers.X-Network=internal"
  - "traefik.http.middlewares.my-conditional-headers.plugin.conditional-headers.rules[1].hosts[0]=*.public.dev.io"
  - "traefik.http.middlewares.my-conditional-headers.plugin.conditional-headers.rules[1].headers.X-App-Kernel-Name=public.valksor.com"
  - "traefik.http.middlewares.my-conditional-headers.plugin.conditional-headers.rules[1].headers.X-Network=public"
Real-World Scenarios
1. Microservices Environment Detection

YAML Configuration:

http:
  middlewares:
    env-detection:
      plugin:
        conditional-headers:
          rules:
            - hosts:
                - "*.dev.company.com"
                - "dev-api.company.com"
              headers:
                X-Environment: "development"
                X-Debug: "true"
                X-Log-Level: "debug"
                X-CORS: "*"
            - hosts:
                - "*.staging.company.com"
                - "staging-api.company.com"
              headers:
                X-Environment: "staging"
                X-Debug: "true"
                X-Log-Level: "info"
                X-CORS: "https://company.com"
            - hosts:
                - "*.company.com"
                - "api.company.com"
              headers:
                X-Environment: "production"
                X-Debug: "false"
                X-Log-Level: "warn"
                X-Cache-Control: "no-cache"
2. API Versioning and Routing

YAML Configuration:

http:
  middlewares:
    api-versioning:
      plugin:
        conditional-headers:
          rules:
            - hosts:
                - "v1.api.company.com"
                - "api-v1.company.com"
              headers:
                X-API-Version: "v1"
                X-API-Deprecated: "false"
                X-API-Sunset: "2024-12-31"
                X-Rate-Limit: "1000/hour"
            - hosts:
                - "v2.api.company.com"
                - "api.company.com"
              headers:
                X-API-Version: "v2"
                X-API-Latest: "true"
                X-Rate-Limit: "5000/hour"
                X-Features: "advanced,caching,webhooks"
            - hosts:
                - "legacy.api.company.com"
              headers:
                X-API-Version: "legacy"
                X-API-Deprecated: "true"
                X-API-Sunset: "2024-06-30"
                X-Rate-Limit: "100/hour"
3. Multi-Tenant SaaS Platform

YAML Configuration:

http:
  middlewares:
    tenant-routing:
      plugin:
        conditional-headers:
          rules:
            - hosts:
                - "*.enterprise.company.com"
                - "company.enterprise.com"
              headers:
                X-Tenant-Type: "enterprise"
                X-Features: "advanced,unlimited,priority-support"
                X-Rate-Limit: "10000/hour"
                X-Storage-Quota: "1TB"
            - hosts:
                - "*.pro.company.com"
                - "company.pro.com"
              headers:
                X-Tenant-Type: "professional"
                X-Features: "standard,api-access,email-support"
                X-Rate-Limit: "5000/hour"
                X-Storage-Quota: "100GB"
            - hosts:
                - "*.company.com"
                - "app.company.com"
              headers:
                X-Tenant-Type: "basic"
                X-Features: "basic,limited"
                X-Rate-Limit: "1000/hour"
                X-Storage-Quota: "10GB"
Advanced Configuration Examples
CDN and Static Asset Handling

YAML Configuration:

http:
  middlewares:
    cdn-headers:
      plugin:
        conditional-headers:
          rules:
            - hosts:
                - "images.cdn.company.com"
                - "img.cdn.company.com"
                - "photos.cdn.company.com"
              headers:
                X-Content-Type: "image"
                X-Cache-Control: "public, max-age=31536000, immutable"
                X-Compress: "gzip"
                X-CDN-Provider: "cloudflare"
            - hosts:
                - "static.cdn.company.com"
                - "assets.cdn.company.com"
                - "js.cdn.company.com"
                - "css.cdn.company.com"
              headers:
                X-Content-Type: "static"
                X-Cache-Control: "public, max-age=86400"
                X-Compress: "gzip,br"
                X-CDN-Provider: "fastly"
            - hosts:
                - "api.company.com"
                - "graph.company.com"
              headers:
                X-Content-Type: "api"
                X-Cache-Control: "no-cache, no-store, must-revalidate"
                X-Rate-Limit: "10000/hour"
                X-API-Version: "v2"
Performance Considerations
  1. Rule Order: Place most specific rules first for better performance
  2. Exact Matches: Use exact host matches when possible for fastest processing
  3. Wildcards: Use wildcard patterns sparingly as they're more expensive
  4. Partial Matches: Avoid partial matches unless necessary, as they're the most expensive

Optimized Configuration Example:

http:
  middlewares:
    optimized-headers:
      plugin:
        conditional-headers:
          rules:
            # Exact matches first (fastest)
            - hosts:
                - "api.company.com"
                - "admin.company.com"
              headers:
                X-Service: "critical"
            # Wildcard matches second
            - hosts:
                - "*.api.company.com"
                - "*.admin.company.com"
              headers:
                X-Service: "api"
            # Partial matches last (most expensive)
            - hosts:
                - "api"
                - "admin"
              headers:
                X-Service: "fallback"

Troubleshooting

Common Issues
1. Headers Not Being Applied

Problem: Headers are not showing up in your application.

Solutions:

  • Check that the middleware is properly applied to your router
  • Verify the hostname matches exactly (including ports)
  • Check rule order - first match wins
  • Use Traefik's debug mode to see plugin activity
# Enable debug logging
traefik --log.level=DEBUG
2. Wildcard Rules Not Working

Problem: Wildcard patterns like *.example.com aren't matching.

Solutions:

  • Ensure the pattern starts with *.
  • Check that your base domain is correct
  • Remember that *.example.com also matches example.com
3. Plugin Not Loading

Problem: Plugin fails to load or Traefik shows plugin errors.

Solutions:

  • Verify plugin name is correct: github.com/valksor/traefik-conditional-headers
  • Check Traefik version compatibility
  • Ensure proper plugin configuration format
  • Review Traefik logs for specific error messages
Debug Configuration

YAML for Debug Mode:

# traefik.yml
log:
  level: DEBUG
accessLog: true

api:
  dashboard: true
  insecure: true

CLI for Debug Mode:

traefik --log.level=DEBUG --accesslog=true --api.dashboard=true --api.insecure=true
Testing Headers

Use curl to verify headers are being set correctly:

# Test specific host
curl -H "Host: api.example.com" http://localhost:8080 -I

# Test wildcard match
curl -H "Host: v1.api.example.com" http://localhost:8080 -I

# Test with custom headers
curl -H "Host: api.example.com" http://localhost:8080 \
  -H "Authorization: Bearer token" -I

Compatibility

Platform Support
  • ✅ Linux (amd64, arm64)
  • ✅ macOS (amd64, arm64)
  • ✅ Windows (amd64)
  • ✅ Docker containers
  • ✅ Kubernetes
  • ✅ Docker Swarm
Known Limitations
  1. Header Values: Only supports string values (no complex objects)
  2. Rule Count: Performance may degrade with 1000+ rules
  3. Partial Matching: Can be overly broad - use with caution
  4. Port Handling: Incoming ports are stripped, but rule patterns should not include ports

Contributing

We welcome contributions! Here's how to get started:

Development Setup
  1. Prerequisites:

    • Go 1.26+
    • Docker
    • Traefik v3.0+
    • golangci-lint v2.12.2 and gawk (required by make):
      # golangci-lint v2.12.2 (installed from source via the Go module proxy)
      go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2
      # gawk
      brew install gawk        # macOS
      sudo apt-get install -y gawk   # Debian/Ubuntu
      
    • goimports + gofumpt (formatters; make quality soft-skips them if absent):
      make tools
      
  2. Clone and Build:

    git clone https://github.com/valksor/traefik-conditional-headers.git
    cd traefik-conditional-headers
    go mod tidy
    
  3. Run Tests & Quality:

    # Run unit tests
    go test -v ./...
    
    # Run integration tests
    go test -v -tags=integration ./...
    
    # Run benchmarks
    go test -bench=. -benchmem ./...
    
    # Full quality gate + tests + coverage (the default target)
    make
    
    # Just the quality gate (format, vet, import-alias check, golangci-lint --fix)
    make quality
    

    Note: make / make quality mutate your working tree — they run gofumpt -w, goimports -w, and golangci-lint run --fix. For a non-mutating check use make lint (bare golangci-lint run). CI runs make and then git diff --exit-code, so any drift the auto-fixers would silently correct fails the build instead.

    Trip-wire: .github/alias.sh's go list shell-out is inert while this plugin is pure stdlib (zero external imports). If you ever add an external dependency, revisit that script's handling of import paths before merging — a crafted path could otherwise reach shell execution in CI.

  4. Local Testing:

    # Start test Traefik instance
    docker run -it --rm \
      -v $(pwd):/plugins-local \
      -p 8080:8080 \
      traefik:v3.5 \
      --experimental.localPlugins.conditional-headers.moduleName=github.com/valksor/traefik-conditional-headers
    
Submitting Changes
  1. Create a feature branch:

    git checkout -b feature/your-feature-name
    
  2. Make your changes and ensure:

    • All tests pass
    • Code follows Go conventions
    • Documentation is updated
    • Examples are tested
  3. Submit a pull request with:

    • Clear description of changes
    • Test cases for new functionality
    • Updated documentation if needed
    • Examples of usage
Code Standards
  • Follow Go Code Review Comments
  • Add comprehensive tests for new features
  • Update documentation for any API changes
  • Use meaningful commit messages
  • Keep backwards compatibility when possible
Testing Guidelines
  • Unit Tests: Test individual functions and methods
  • Integration Tests: Test complete request flows
  • Edge Cases: Test unusual hostnames and configurations
  • Performance: Benchmark with realistic rule sets
  • Compatibility: Test with different Traefik versions
Release Process

This project follows semantic versioning and has released 3 versions:

  1. v0.0.1 (2025-10-26): Initial release with core functionality
  2. v0.0.2 (2025-10-26): Configuration file naming update
  3. v0.0.3 (2025-10-26): Enhanced CI/CD and stability improvements

Future releases will follow this process:

  1. Finalize features and testing
  2. Update version in documentation
  3. Update CHANGELOG.md with release notes
  4. Create git tag: git tag v0.0.x
  5. Push to repository
  6. Create GitHub release
  7. Publish to Traefik plugin catalog
  8. Update documentation with new version information

License

This project is licensed under the BSD 3-Clause License - see the LICENSE file for details.

Support

Documentation

Overview

Package traefik_conditional_headers provides a Traefik middleware plugin for setting HTTP headers conditionally based on the incoming request hostname.

The plugin supports multiple matching strategies:

  • Exact host matching (e.g., "api.example.com")
  • Wildcard subdomain matching (e.g., "*.example.com")
  • Partial string matching (e.g., "api" matches "my-api.example.com")

Rules are evaluated in order, and the first matching rule wins. Multiple hosts can share the same header configuration within a single rule.

Example configuration:

rules:
  - hosts:
      - api.example.com
      - "*.dev.example.com"
    headers:
      X-Environment: "development"
      X-Service: "api-gateway"

Performance considerations:

  • The plugin processes rules sequentially, so place most specific rules first
  • Wildcard matching is more expensive than exact matching
  • Consider rule order for optimal performance in large rule sets

The plugin is compatible with yaegi (Traefik's plugin interpreter) and handles nil next handlers gracefully to prevent panics during configuration testing.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func New

func New(ctx context.Context, next http.Handler, config *Config, name string) (http.Handler, error)

New creates a new instance of the conditional headers plugin. This function is called by Traefik when the plugin is loaded.

Parameters:

  • ctx: The context for the plugin instance (currently unused but required by interface)
  • next: The next handler in the Traefik middleware chain
  • config: The plugin configuration containing rules
  • name: The name of this plugin instance

Returns:

  • http.Handler: The plugin handler instance
  • error: Always returns nil as this plugin cannot fail to initialize

The function gracefully handles nil next handlers to maintain compatibility with yaegi (Traefik's plugin interpreter) during configuration testing.

Types

type Config

type Config struct {
	// Rules is the ordered list of hostname-to-header mapping rules.
	// Rules are evaluated in sequence, with the first matching rule being applied.
	Rules []Rule `json:"rules"`
}

Config represents the plugin configuration structure. It contains a list of rules that define which headers to set based on hostname matching.

func CreateConfig

func CreateConfig() *Config

CreateConfig creates a new default configuration for the plugin. This function is called by Traefik to initialize the plugin with default values.

Returns:

  • *Config: A pointer to a configuration with empty rules slice.

Example:

config := CreateConfig()
config.Rules = []Rule{
    {
        Hosts: []string{"example.com"},
        Headers: map[string]string{"X-Custom": "value"},
    },
}

type Rule

type Rule struct {
	// Hosts is the list of hostname patterns that should trigger this rule.
	// Supports exact matches, wildcards (e.g., "*.example.com"), and partial matches.
	Hosts []string `json:"hosts"`

	// Headers is the map of HTTP header names and values to be set when this rule matches.
	// All headers in this map will be set on the matching requests.
	Headers map[string]string `json:"headers"`
}

Rule defines a mapping between host patterns and HTTP headers to be set. Multiple hosts can be specified in a single rule to share the same headers.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL