Skip to content

Environment Variables in .htaccess

Learn how to use Apache's SetEnv, SetEnvIf, and PassEnv directives in .htaccess to dynamically configure behavior, pass application settings, and implement conditional logic based on request attributes.

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

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:

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 .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>
  UnsetEnv PATH

  UnsetEnv DEBUG_MODE
</IfModule>

Common Mistake: PassEnv for Variables That Do Not Exist

Wrong approach:

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>
  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):

Apache 2.4+ syntaxapache
<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

Environment-based application configurationapache
<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:

config.phpphp
<?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:

/var/www/html/.htaccessapache
<IfModule mod_env.c>
  SetEnv APP_SECTION main
</IfModule>
/var/www/html/blog/.htaccessapache
<IfModule mod_env.c>
  SetEnv APP_SECTION blog
</IfModule>
/var/www/html/api/.htaccessapache
<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>
Application codephp
<?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:

Production .htaccess with environment variablesapache
<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.

Navigation