Environment variables in Apache are key-value pairs that exist for the lifetime of a single request. They let you tag incoming requests with metadata, make decisions based on request characteristics, pass information to your backend applications, and control how Apache modules behave, all without touching your application code.
This guide covers how to set, conditionally set, and pass environment variables using .htaccess, and walks through practical use cases including browser-specific behavior, conditional logging, access control, and passing configuration data to PHP and CGI applications.
SetEnv and SetEnvIf
Apache provides two primary directives for creating environment variables: SetEnv for unconditional assignment and SetEnvIf for conditional assignment based on request attributes.
SetEnv
The SetEnv directive (provided by mod_env) sets an environment variable to a fixed value for every request processed in the current scope.
The syntax is:
SetEnv variable-name valueA simple example:
<IfModule mod_env.c>
SetEnv APP_ENV production
SetEnv DB_HOST db.example.com
SetEnv SITE_NAME "My Website"
</IfModule>Every request handled by Apache in this directory (and its subdirectories) will have these three environment variables available. Your application code can then read them.
For example, in PHP:
$environment = getenv('APP_ENV'); // "production"
$dbHost = getenv('DB_HOST'); // "db.example.com"
$siteName = getenv('SITE_NAME'); // "My Website"
// Also available via $_SERVER
$environment = $_SERVER['APP_ENV'];SetEnvIf
The SetEnvIf directive (provided by mod_setenvif) sets an environment variable only when a condition is met. The condition is based on request attributes such as headers, the remote IP address, the request method, or even another environment variable.
The syntax is:
SetEnvIf attribute regex variable=valueOr to simply set a variable (as a flag, with no specific value):
SetEnvIf attribute regex variableThe attribute can be any of the following:
| Attribute | Description |
|---|---|
Remote_Host |
The client's hostname (if DNS lookup is enabled) |
Remote_Addr |
The client's IP address |
Server_Addr |
The server's IP address |
Request_Method |
The HTTP method (GET, POST, PUT, etc.) |
Request_Protocol |
The protocol and version (e.g., HTTP/1.1) |
Request_URI |
The requested URI path |
| Any HTTP header | Referenced by header name (e.g., User-Agent, Host) |
Here are some practical examples:
<IfModule mod_setenvif.c>
SetEnvIf Remote_Addr "^192\.168\.1\." internal_network
SetEnvIf User-Agent "Googlebot" is_bot
SetEnvIf Request_Method "POST" is_post_request
SetEnvIf Request_URI "^/api/" is_api_request
SetEnvIf Host "^staging\." APP_ENV=staging
</IfModule>SetEnvIfNoCase
SetEnvIfNoCase works exactly like SetEnvIf but performs case-insensitive regex matching. This is particularly useful for matching headers where the casing may vary between clients:
<IfModule mod_setenvif.c>
SetEnvIfNoCase User-Agent "googlebot" is_bot
SetEnvIfNoCase User-Agent "mobile|android|iphone" is_mobile
</IfModule>SetEnvIf Based on Another Variable
You can chain conditions by testing whether a previously set environment variable exists:
<IfModule mod_setenvif.c>
SetEnvIfNoCase User-Agent "Googlebot|Bingbot|Slurp" is_bot
SetEnvIf is_bot "^1$" log_separately
</IfModule>When SetEnvIf sets a variable without an explicit value (like is_bot), the variable is set to 1. You can then test for that value in subsequent directives.
PassEnv
The PassEnv directive (also from mod_env) passes an environment variable from the server's system environment (the shell environment where Apache was started) into the Apache request environment.
The syntax is:
PassEnv variable-name [variable-name] ...Example:
<IfModule mod_env.c>
PassEnv HOME
PassEnv DATABASE_URL
PassEnv API_SECRET_KEY
</IfModule>If the system environment has DATABASE_URL=postgres://user:pass@host/db when Apache starts, every request will have access to that variable.
In PHP:
$dbUrl = getenv('DATABASE_URL'); // "postgres://user:pass@host/db"When to Use PassEnv
PassEnv is valuable when:
- You store configuration secrets in system environment variables (common in 12-factor app methodology and container deployments).
- You deploy the same application across multiple environments (development, staging, production) and use environment variables to differentiate them.
- You want to keep sensitive credentials out of
.htaccessfiles, which might be accidentally committed to version control.
UnsetEnv
The counterpart to SetEnv and PassEnv is UnsetEnv, which removes an environment variable so it is not passed to CGI scripts or backend applications:
<IfModule mod_env.c>
UnsetEnv PATH
UnsetEnv DEBUG_MODE
</IfModule>Common Mistake: PassEnv for Variables That Do Not Exist
Wrong approach:
PassEnv MY_SECRETIf MY_SECRET is not defined in the system environment when Apache starts, PassEnv silently ignores it. Your application then receives null or an empty string, which can cause subtle bugs that are hard to diagnose.
Correct approach:
Always provide a fallback in your application code:
$secret = getenv('MY_SECRET');
if ($secret === false) {
error_log('WARNING: MY_SECRET environment variable is not set');
// Use a default or throw an exception
}Or set a default value in .htaccess that gets overridden by PassEnv when the system variable exists:
<IfModule mod_env.c>
SetEnv MY_SECRET "default-development-key"
PassEnv MY_SECRET
</IfModule>Conditional Behavior with Environment Variables
Once environment variables are set, you can use them throughout your Apache configuration to control behavior conditionally. Several Apache modules support environment variable-based conditions.
Conditional Headers with mod_headers
You can add, modify, or remove HTTP headers based on the presence of an environment variable using the env= condition:
<IfModule mod_setenvif.c>
SetEnvIfNoCase User-Agent "Googlebot|Bingbot" is_bot
</IfModule>
<IfModule mod_headers.c>
Header set X-Served-To "bot" env=is_bot
Header unset X-Debug-Info env=!is_bot
</IfModule>The env=is_bot syntax means "apply this directive only if the is_bot variable is set." The env=!is_bot syntax (with exclamation mark) means "apply this directive only if the is_bot variable is not set."
Conditional Access Control
You can allow or deny access based on environment variables. This works with both the legacy mod_access (Apache 2.2) and the modern mod_authz_core (Apache 2.4):
<IfModule mod_setenvif.c>
SetEnvIf Remote_Addr "^10\.0\.0\." internal_network
</IfModule>
<IfModule mod_authz_core.c>
<Location "/admin">
Require env internal_network
</Location>
</IfModule>Only clients from the 10.0.0.* IP range will have the internal_network variable set, and only those clients can access the /admin location.
Conditional Logging
Environment variables are commonly used to control what gets logged. You can exclude certain requests from your access logs to reduce noise:
<IfModule mod_setenvif.c>
SetEnvIf Request_URI "\.(jpg|jpeg|png|gif|ico|css|js|woff2)$" no_log
SetEnvIf Request_URI "^/health-check$" no_log
SetEnvIfNoCase User-Agent "UptimeRobot|Pingdom|StatusCake" no_log
</IfModule>Conditional Behavior with mod_rewrite
You can set environment variables within rewrite rules using the [E=VAR:VALUE] flag, and then test for them in subsequent rules or conditions:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTPS} =on
RewriteRule ^ - [E=PROTO:https]
RewriteCond %{HTTPS} !=on
RewriteRule ^ - [E=PROTO:http]
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
RewriteRule ^(.*)$ %{ENV:PROTO}://%1/$1 [R=301,L]
</IfModule>You can also test for environment variables in RewriteCond:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{ENV:is_api_request} =1
RewriteRule ^(.*)$ /api/handler.php [L]
</IfModule>Use Cases
Let's explore practical, real-world scenarios where environment variables solve common problems.
Browser-Specific Rules
Different browsers sometimes need different treatment. While modern web development generally avoids browser-specific hacks, there are legitimate cases where server-side detection is useful.
Serving Different Cache Headers for Older Browsers
<IfModule mod_setenvif.c>
SetEnvIfNoCase User-Agent "MSIE|Trident" is_legacy_browser
SetEnvIfNoCase User-Agent "Opera Mini|UCBrowser" is_legacy_browser
</IfModule>
<IfModule mod_headers.c>
Header set Cache-Control "no-cache, no-store, must-revalidate" env=is_legacy_browser
Header set Cache-Control "public, max-age=31536000, immutable" env=!is_legacy_browser
</IfModule>Serving Specific Content to Bots
Search engine bots and social media crawlers sometimes benefit from different treatment. For example, you might want to serve pre-rendered HTML to bots that cannot execute JavaScript:
<IfModule mod_setenvif.c>
SetEnvIfNoCase User-Agent "Googlebot|Bingbot|Slurp|DuckDuckBot|facebookexternalhit|Twitterbot|LinkedInBot" is_crawler
</IfModule>
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{ENV:is_crawler} =1
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ /prerendered/$1.html [L]
</IfModule>Fixing Known Browser Bugs with Headers
Some browsers have specific bugs that can be worked around with response headers:
<IfModule mod_setenvif.c>
SetEnvIfNoCase User-Agent "MSIE 8" ie8
SetEnvIfNoCase User-Agent "Safari" safari_browser
</IfModule>
<IfModule mod_headers.c>
Header set X-UA-Compatible "IE=edge" env=ie8
</IfModule>Passing Variables to Applications
One of the most practical uses of environment variables is passing configuration data from the server to your application without hardcoding values in your source code.
Application Configuration
<IfModule mod_env.c>
SetEnv APP_ENV production
SetEnv APP_DEBUG false
SetEnv APP_URL https://example.com
SetEnv DB_CONNECTION mysql
SetEnv DB_HOST 127.0.0.1
SetEnv DB_PORT 3306
SetEnv DB_DATABASE myapp
SetEnv DB_USERNAME appuser
SetEnv MAIL_DRIVER smtp
SetEnv MAIL_HOST smtp.mailtrap.io
SetEnv MAIL_PORT 587
</IfModule>Your PHP application reads these values:
<?php
return [
'env' => getenv('APP_ENV') ?: 'development',
'debug' => getenv('APP_DEBUG') === 'true',
'url' => getenv('APP_URL') ?: 'http://localhost',
'db' => [
'host' => getenv('DB_HOST') ?: '127.0.0.1',
'port' => getenv('DB_PORT') ?: 3306,
'database' => getenv('DB_DATABASE') ?: 'myapp',
'username' => getenv('DB_USERNAME') ?: 'root',
],
];Per-Directory Application Behavior
You can use different .htaccess files in different directories to change application behavior:
<IfModule mod_env.c>
SetEnv APP_SECTION main
</IfModule><IfModule mod_env.c>
SetEnv APP_SECTION blog
</IfModule><IfModule mod_env.c>
SetEnv APP_SECTION api
SetEnv API_VERSION v2
SetEnv RATE_LIMIT 100
</IfModule>The application can then adjust its behavior based on which section is being accessed, without needing complex URL parsing logic.
Feature Flags
Environment variables make excellent feature flags that you can toggle without deploying code:
<IfModule mod_env.c>
SetEnv FEATURE_NEW_CHECKOUT true
SetEnv FEATURE_DARK_MODE false
SetEnv FEATURE_BETA_API true
</IfModule><?php
if (getenv('FEATURE_NEW_CHECKOUT') === 'true') {
// Show new checkout flow
} else {
// Show legacy checkout flow
}To disable a feature, you simply change true to false in .htaccess and reload Apache. No code deployment needed.
Request Tagging for Debugging
During development or incident investigation, you can tag requests with metadata that flows through to your application logs:
<IfModule mod_setenvif.c>
SetEnvIf Remote_Addr "^203\.0\.113\." REQUEST_SOURCE=office
SetEnvIf Remote_Addr "^198\.51\.100\." REQUEST_SOURCE=vpn
SetEnvIf Request_URI "^/api/v1/" API_VERSION=v1
SetEnvIf Request_URI "^/api/v2/" API_VERSION=v2
SetEnvIfNoCase X-Request-ID "(.+)" REQUEST_TRACE_ID=$1
</IfModule>Your application can then include these tags in log entries:
error_log(sprintf(
"[%s] [source=%s] [api=%s] Request processed",
getenv('REQUEST_TRACE_ID') ?: 'no-trace',
getenv('REQUEST_SOURCE') ?: 'external',
getenv('API_VERSION') ?: 'none'
));Combining Multiple Techniques
Here is a complete, realistic .htaccess that combines several environment variable techniques:
<IfModule mod_setenvif.c>
SetEnvIf Remote_Addr "^10\." internal_traffic
SetEnvIf Remote_Addr "^192\.168\." internal_traffic
SetEnvIfNoCase User-Agent "bot|crawl|spider|slurp" is_bot
SetEnvIfNoCase User-Agent "Mobile|Android|iPhone|iPad" is_mobile
SetEnvIf Request_URI "\.(css|js|png|jpg|gif|ico|woff2)$" no_log
SetEnvIf Request_URI "^/health$" no_log
</IfModule>
<IfModule mod_env.c>
SetEnv APP_ENV production
SetEnv APP_DEBUG false
PassEnv DATABASE_URL
PassEnv SECRET_KEY
</IfModule>
<IfModule mod_headers.c>
Header set X-Debug-Info "enabled" env=internal_traffic
Header unset X-Debug-Info env=!internal_traffic
Header set X-Bot-Detected "true" env=is_bot
</IfModule>
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ /index.php [L]
</IfModule>This configuration detects request characteristics early with SetEnvIf, passes application secrets securely with PassEnv, adjusts response headers conditionally, and routes all dynamic requests through a front controller. Each piece works together through the common thread of environment variables.
Environment variables are the glue between your web server and your application. They let you make server-level decisions based on request characteristics, pass configuration without hardcoding, and create flexible setups that adapt to different environments. Master SetEnv for static configuration, SetEnvIf for conditional logic, and PassEnv for secure credential management, and you will have a powerful toolkit for building robust, maintainable server configurations.