Here's what I observed in my development environment (Apache 2.2.29 on macOS):
HTTP/1.1 200 OK
Server: Apache/2.2.29 (Unix)
Content-Type: application/json; charset=utf-8
# Missing all my CORS headers here!
First, let's confirm the essentials are in place:
# Check if module is loaded
httpd -M | grep headers_module
# Should return: headers_module (shared)
# Verify .htaccess permissions
<Directory "/path/to/your/project">
AllowOverride All
# ... other directives
</Directory>
After hours of debugging, I discovered that the module execution order was interfering with my headers. Here's what fixed it:
# In httpd.conf or virtual host config
<IfModule mod_headers.c>
# Must come BEFORE mod_rewrite
Header set Access-Control-Allow-Origin "*"
Header set Access-Control-Allow-Methods "POST, GET, PUT, OPTIONS, PATCH, DELETE"
Header set Access-Control-Allow-Headers "X-Accept-Charset,X-Accept,Content-Type"
</IfModule>
<IfModule mod_rewrite.c>
RewriteEngine On
# ... your rewrite rules
</IfModule>
When basic checks don't work, try these advanced methods:
# 1. Check for conflicting directives
apachectl -S
apachectl configtest
# 2. Enable trace logging
LogLevel debug
TraceEnable On
# 3. Test with minimal configuration
# Create a test .htaccess with ONLY headers:
Header set X-Test-Header "HelloWorld"
Watch out for these scenarios where headers might not apply:
- When serving static files (AddType directives may interfere)
- With certain MIME types (especially application/json)
- When using PHP's header() function that overrides Apache headers
# Example workaround for JSON responses
<FilesMatch "\.(json)$">
Header set Access-Control-Allow-Origin "*"
Header set Access-Control-Allow-Methods "GET, POST, OPTIONS"
</FilesMatch>
Here's the bulletproof configuration that worked across all my environments:
# In .htaccess
<IfModule mod_headers.c>
# CORS for API requests
SetEnvIf Origin "^(.+)$" cors=$1
Header set Access-Control-Allow-Origin "%{cors}e" env=cors
Header merge Vary "Origin"
# Preflight requests
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ $1 [R=200,L]
Header set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
Header set Access-Control-Allow-Headers "Content-Type, Authorization"
Header set Access-Control-Allow-Credentials "true"
Header set Access-Control-Max-Age "1728000"
</IfModule>
After implementing this, my headers appeared consistently in all responses:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
# ... other headers
After setting up Apache 2.2.29 on my Mac development environment, I encountered an odd issue where CORS headers defined in my .htaccess file weren't being applied, even though the headers_module was clearly loaded. Here's what my configuration looked like:
Header set Access-Control-Allow-Origin "*"
Header set Access-Control-Allow-Methods "POST, GET, PUT, OPTIONS, PATCH, DELETE"
Header set Access-Control-Allow-Headers "X-Accept-Charset,X-Accept,Content-Type"
First, I confirmed the headers module was indeed loaded:
$ httpd -M | grep headers
headers_module (shared)
And the module file existed in the specified location:
$ ls -la /usr/libexec/apache2/mod_headers.so
-rwxr-xr-x 1 root wheel 38952 Dec 15 2014 /usr/libexec/apache2/mod_headers.so
The virtual host configuration appeared correct with proper AllowOverride settings:
Options Indexes FollowSymLinks MultiViews
AllowOverride All
Require all granted
After extensive testing, I found the issue was related to Apache's processing order. The headers weren't being applied because:
- The directives were being processed after PHP had already set headers
- There was a conflict with mod_rewrite rules in the same .htaccess
Here's the modified .htaccess that finally worked:
# Set headers early in the processing pipeline
Header always set Access-Control-Allow-Origin "*"
Header always set Access-Control-Allow-Methods "POST, GET, PUT, OPTIONS, PATCH, DELETE"
Header always set Access-Control-Allow-Headers "X-Accept-Charset,X-Accept,Content-Type"
# For preflight requests
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ $1 [R=200,L]
RewriteEngine On
RewriteBase /cms/public
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !^/(favicon\.ico|apple-touch-icon.*\.png)$ [NC]
RewriteRule (.+) index.php?p=$1 [QSA,L]
The key changes were:
- Using
Header always set
instead of justHeader set
- Adding specific handling for OPTIONS requests
- Ensuring headers are set before rewrite rules execute
After implementing these changes, the response headers now correctly include:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, GET, PUT, OPTIONS, PATCH, DELETE
Access-Control-Allow-Headers: X-Accept-Charset,X-Accept,Content-Type
If you're still facing issues, consider:
# Check if headers are being unset elsewhere
grep -r "Header unset" /etc/apache2/
# Test with minimal configuration
# Create test file headers-test.conf:
Header set Test-Header "Works"