Skip to main content

Environment Variables in .htaccess

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 value

A 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'];
note

SetEnv variables are set late in the request processing pipeline, during the content handling phase. This means they are not available to early-phase directives like RewriteCond. If you need an environment variable during the rewrite phase, use SetEnvIf or the [E=VAR:VALUE] flag on a RewriteRule instead.

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=value

Or to simply set a variable (as a flag, with no specific value):

SetEnvIf attribute regex variable

The attribute can be any of the following:

AttributeDescription
Remote_HostThe client's hostname (if DNS lookup is enabled)
Remote_AddrThe client's IP address
Server_AddrThe server's IP address
Request_MethodThe HTTP method (GET, POST, PUT, etc.)
Request_ProtocolThe protocol and version (e.g., HTTP/1.1)
Request_URIThe requested URI path
Any HTTP headerReferenced by header name (e.g., User-Agent, Host)

Here are some practical examples:

<IfModule mod_setenvif.c>

# Set a variable based on the client's IP address
SetEnvIf Remote_Addr "^192\.168\.1\." internal_network

# Set a variable based on the User-Agent header
SetEnvIf User-Agent "Googlebot" is_bot

# Set a variable based on the request method
SetEnvIf Request_Method "POST" is_post_request

# Set a variable based on the requested URL
SetEnvIf Request_URI "^/api/" is_api_request

# Set a variable with a specific value based on the Host header
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>
# Match "Googlebot", "googlebot", "GoogleBot", etc.
SetEnvIfNoCase User-Agent "googlebot" is_bot

# Match various mobile user agents regardless of case
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>
# First, detect bots
SetEnvIfNoCase User-Agent "Googlebot|Bingbot|Slurp" is_bot

# Then, based on is_bot being set, set another variable
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.

tip

SetEnvIf variables are set early in the request processing pipeline, before most other modules run. This makes them available to mod_rewrite, mod_log, mod_headers, and access control directives, unlike SetEnv variables which are set later.

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 .htaccess files, 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>
# Don't expose the server's PATH to applications
UnsetEnv PATH

# Remove a variable that was set at a higher level
UnsetEnv DEBUG_MODE
</IfModule>

Common Mistake: PassEnv for Variables That Do Not Exist

Wrong approach:

# This silently does nothing if MY_SECRET doesn't exist in the system environment
PassEnv MY_SECRET

If 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>
# Set a default
SetEnv MY_SECRET "default-development-key"

# Override with system variable if it exists
PassEnv MY_SECRET
</IfModule>
caution

Be aware that PassEnv only works with variables that existed in Apache's environment at startup time. If you set a system environment variable after Apache has already started, you must restart (or gracefully reload) Apache for PassEnv to pick it up.

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>
# Send a special header only for bot requests
Header set X-Served-To "bot" env=is_bot

# Remove a header for non-bot requests
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):

Apache 2.4+ syntax
<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>
# Don't log requests for static assets
SetEnvIf Request_URI "\.(jpg|jpeg|png|gif|ico|css|js|woff2)$" no_log

# Don't log health check requests
SetEnvIf Request_URI "^/health-check$" no_log

# Don't log requests from monitoring services
SetEnvIfNoCase User-Agent "UptimeRobot|Pingdom|StatusCake" no_log
</IfModule>

# In the server config or virtual host (not typically in .htaccess):
# CustomLog /var/log/apache2/access.log combined env=!no_log
note

The CustomLog directive with the env= condition can only be used in the server config or virtual host context, not in .htaccess. However, the SetEnvIf directives that set the variables can be placed in .htaccess. This pattern is shown here because it is one of the most common uses of environment variables in Apache.

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

# Detect protocol and store in an environment variable
RewriteCond %{HTTPS} =on
RewriteRule ^ - [E=PROTO:https]
RewriteCond %{HTTPS} !=on
RewriteRule ^ - [E=PROTO:http]

# Use the variable in a later rule
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

# Only apply this rule if the request is flagged as an API request
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>
# Detect Internet Explorer
SetEnvIfNoCase User-Agent "MSIE|Trident" is_legacy_browser

# Detect very old mobile browsers
SetEnvIfNoCase User-Agent "Opera Mini|UCBrowser" is_legacy_browser
</IfModule>

<IfModule mod_headers.c>
# Disable caching for legacy browsers to avoid rendering issues
Header set Cache-Control "no-cache, no-store, must-revalidate" env=is_legacy_browser

# Use aggressive caching for modern browsers
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

# Serve pre-rendered version to crawlers
RewriteCond %{ENV:is_crawler} =1
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ /prerendered/$1.html [L]
</IfModule>
warning

Be careful with User-Agent-based rules. User-Agent strings can be easily spoofed, so never use them for security decisions. They are appropriate for optimization and compatibility adjustments, but not for access control.

Fixing Known Browser Bugs with Headers

Some browsers have specific bugs that can be worked around with response headers:

<IfModule mod_setenvif.c>
# Old versions of IE had issues with SVG content type
SetEnvIfNoCase User-Agent "MSIE 8" ie8

# Some browsers need explicit CORS for fonts
SetEnvIfNoCase User-Agent "Safari" safari_browser
</IfModule>

<IfModule mod_headers.c>
# Force IE8 to use the latest rendering engine
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

Environment-based application configuration
<IfModule mod_env.c>
# Application environment
SetEnv APP_ENV production
SetEnv APP_DEBUG false
SetEnv APP_URL https://example.com

# Database configuration
SetEnv DB_CONNECTION mysql
SetEnv DB_HOST 127.0.0.1
SetEnv DB_PORT 3306
SetEnv DB_DATABASE myapp
SetEnv DB_USERNAME appuser

# Third-party service keys
SetEnv MAIL_DRIVER smtp
SetEnv MAIL_HOST smtp.mailtrap.io
SetEnv MAIL_PORT 587
</IfModule>

Your PHP application reads these values:

config.php
<?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',
],
];
caution

Never store passwords or secret API keys directly in .htaccess files that might be committed to version control. Use PassEnv to pull secrets from system environment variables, or use a .env file loaded by your application framework (like Laravel's Dotenv). If you must use SetEnv for secrets, make sure your .htaccess file is excluded from your repository and protected from direct HTTP access.

Per-Directory Application Behavior

You can use different .htaccess files in different directories to change application behavior:

/var/www/html/.htaccess
<IfModule mod_env.c>
SetEnv APP_SECTION main
</IfModule>
/var/www/html/blog/.htaccess
<IfModule mod_env.c>
SetEnv APP_SECTION blog
</IfModule>
/var/www/html/api/.htaccess
<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>
# Enable or disable features by changing these values
SetEnv FEATURE_NEW_CHECKOUT true
SetEnv FEATURE_DARK_MODE false
SetEnv FEATURE_BETA_API true
</IfModule>
Application code
<?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>
# Tag requests from specific IP ranges
SetEnvIf Remote_Addr "^203\.0\.113\." REQUEST_SOURCE=office
SetEnvIf Remote_Addr "^198\.51\.100\." REQUEST_SOURCE=vpn

# Tag requests by path pattern
SetEnvIf Request_URI "^/api/v1/" API_VERSION=v1
SetEnvIf Request_URI "^/api/v2/" API_VERSION=v2

# Tag requests with specific headers (useful for tracing)
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:

Production .htaccess with environment variables
# === Environment Detection ===
<IfModule mod_setenvif.c>
# Detect internal traffic
SetEnvIf Remote_Addr "^10\." internal_traffic
SetEnvIf Remote_Addr "^192\.168\." internal_traffic

# Detect bots
SetEnvIfNoCase User-Agent "bot|crawl|spider|slurp" is_bot

# Detect mobile devices
SetEnvIfNoCase User-Agent "Mobile|Android|iPhone|iPad" is_mobile

# Don't log static assets or health checks
SetEnvIf Request_URI "\.(css|js|png|jpg|gif|ico|woff2)$" no_log
SetEnvIf Request_URI "^/health$" no_log
</IfModule>

# === Application Configuration ===
<IfModule mod_env.c>
SetEnv APP_ENV production
SetEnv APP_DEBUG false
PassEnv DATABASE_URL
PassEnv SECRET_KEY
</IfModule>

# === Conditional Headers ===
<IfModule mod_headers.c>
# Add debug headers only for internal traffic
Header set X-Debug-Info "enabled" env=internal_traffic
Header unset X-Debug-Info env=!internal_traffic

# Add bot identification header
Header set X-Bot-Detected "true" env=is_bot
</IfModule>

# === Rewrite Rules ===
<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.