Project

General

Profile

Actions

Conditional Request Headers

Module: mod_setenv

Description

mod_setenv modifies request headers (from clients), response headers (to clients), and the environment (for CGI).

Note: mod_setenv needs to be listed prior to mod_redirect in the server.modules list in lighttpd.conf so that mod_setenv can set headers prior to the redirect. (Listing modules alphabetically is a common mistake. e.g. #2946)

To set "Cache-Control" and "Expires" caching response headers, prefer mod_expire instead of mod_setenv.
To set "Content-Encoding", prefer mod_deflate.

Aside: arbitrarily complex header manipulation can be achieved using mod_magnet (e.g. lua mod_setenv) if mod_setenv config options below are not flexible enough for your needs.

To use mod_setenv: server.modules += ( "mod_setenv" )

Options

setenv.add-response-header
Adds a header to the HTTP response sent to the client:

setenv.add-response-header = ("My-Custom-Header" => "my-custom-value")

setenv.add-request-header
Adds a header to the HTTP request that was received from the client:

setenv.add-request-header = ("X-Proxy" => "my.server.name")

setenv.add-environment
Adds a value to the process environment (aka environment variables) that is passed to the external applications:

setenv.add-environment = ( 
  "TRAC_ENV" => "lighttpd",
  "RAILS_ENV" => "production" 
)

setenv.set-request-header (since 1.4.46)
setenv.set-response-header (since 1.4.46)
setenv.set-environment (since 1.4.46)
These directives set the given values, rather than appending the given values to the headers or environment. These directives take precedence over the setenv.add-* counterparts. Set a blank value to remove request header or remove response header.

HTTP Strict Transport Security (HSTS)

A typical HSTS configuration sets max-age to 365 days (31536000 seconds). If testing, please use a shorter max-age (e.g. max-age=600) until you are more confident with your configuration. Do not append ; preload to the header value unless you understand the consequences.

setenv.set-response-header += ("Strict-Transport-Security" => "max-age=31536000; includeSubDomains")

See also Using setenv in multiple configuration conditions below for setting HSTS along with other setenv directives.

Opt-out of Google's Federated Learning of Cohorts (FLoC)

EFF article: Google’s FLoC Is a Terrible Idea

setenv.set-response-header += ( "Permissions-Policy" => "interest-cohort=()" )

Using setenv in multiple configuration conditions

The way that lighttpd processes its configuration syntax may not be intuitive to some, but the results must be deterministic. lighttpd parses its configuration at startup. lighttpd does not merge lists from multiple matching conditions. For each specific configuration directive, the last condition that matches a client request and contains that specific directive is the configuration used for that specific directive for that client request. setenv-add-response-header and setenv-set-response-header are distinct configuration directives.

For example: a request for "/abc/def/ghi" will match all three conditions, and even though the directive is setenv-add-response-header, only the last one will be used for the request, adding the response header "Custom-Header: value-C"

$HTTP["url"] =~ "^/" {
    setenv.add-response-header = ("Custom-Header" => "value-A")
}
$HTTP["url"] =~ "^/abc/" {
    setenv.add-response-header = ("Custom-Header" => "value-B")
}
$HTTP["url"] =~ "^/abc/def/" {
    setenv.add-response-header = ("Custom-Header" => "value-C")
}

Order matters. The order in which you write the configuration matters. For the same request for "/abc/def/ghi" but with the configuration directives listed in a different order, again, the last matching condition will be used for the request, in this case adding the response header "Custom-Header: value-A"

$HTTP["url"] =~ "^/abc/def/" {
    setenv.add-response-header = ("Custom-Header" => "value-C")
}
$HTTP["url"] =~ "^/abc/" {
    setenv.add-response-header = ("Custom-Header" => "value-B")
}
$HTTP["url"] =~ "^/" {
    setenv.add-response-header = ("Custom-Header" => "value-A")
}

The same is true for +=. The += applies only to the list within the current scope, within the current configuration condition. The += does not have an effect across different configuration conditions. For the same request for "/abc/def/ghi" but with the configuration directives listed below, again, the last matching condition will be used for the request, in this case adding the response headers "Custom-Header1: value-C" and "Custom-Header2: value-C"

$HTTP["url"] =~ "^/" {
    setenv.add-response-header = ("Custom-Header1" => "value-A")
    setenv.add-response-header+= ("Custom-Header2" => "value-A")
}
$HTTP["url"] =~ "^/abc/" {
    setenv.add-response-header = ("Custom-Header1" => "value-B")
    setenv.add-response-header+= ("Custom-Header2" => "value-B")
}
$HTTP["url"] =~ "^/abc/def/" {
    setenv.add-response-header = ("Custom-Header1" => "value-C")
    setenv.add-response-header+= ("Custom-Header2" => "value-C")
}

The following is one method to define a setenv set of policy headers into a variable and to reuse the set:

var.response_header_policy = (
  #"strict-transport-security" => "max-age=31536000; includeSubDomains", # preferred for sites to designate HSTS
  "strict-transport-security" => "max-age=300; includeSubDomains",       # minimize damage for those who blindly cut-n-paste
  "content-security-policy" => "default-src https:",
  "x-frame-options" => "SAMEORIGIN",
  "x-content-type-options" => "nosniff",
  "x-xss-protection" => "0",                                             # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection
  "permissions-policy" => "interest-cohort=()"                           # opt-out of Google's FLoC
)

$HTTP["url"] =~ "^/" {
    setenv.set-response-header = var.response_header_policy
    setenv.set-response-header+= ("custom-header" => "value-A")
}
$HTTP["url"] =~ "^/abc/" {
    setenv.set-response-header = var.response_header_policy
    setenv.set-response-header+= ("custom-header" => "value-B")
}
$HTTP["url"] =~ "^/abc/def/" {
    setenv.set-response-header = var.response_header_policy
    setenv.set-response-header+= ("custom-header" => "value-C")
}

Another method is to take advantage of limited += list merging from a parent condition. As lighttpd parses the config at startup (not at runtime), this merging with += is limited to inheriting from direct parent conditions, not across peer conditions at the same level of nesting. Also, while new entries (keys) can be added to the list, list keys inherited from parent conditions may not be duplicated or overwritten with this method.

setenv.set-response-header = (
  #"strict-transport-security" => "max-age=31536000; includeSubDomains", # preferred for sites to designate HSTS
  "strict-transport-security" => "max-age=300; includeSubDomains",       # minimize damage for those who blindly cut-n-paste
  "content-security-policy" => "default-src https:",
  "x-frame-options" => "SAMEORIGIN",
  "x-content-type-options" => "nosniff",
  "x-xss-protection" => "0",                                             # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection
  "permissions-policy" => "interest-cohort=()"                           # opt-out of Google's FLoC
)

$HTTP["url"] =~ "^/" {
    setenv.set-response-header+= ("custom-header" => "value-A")
}
$HTTP["url"] =~ "^/abc/" {
    setenv.set-response-header+= ("custom-header" => "value-B")
}
$HTTP["url"] =~ "^/abc/def/" {
    setenv.set-response-header+= ("custom-header" => "value-C")
}

Some policy response headers should be sent in all responses, but some need not be sent in all responses, e.g. for images, scripts, stylesheets, etc. You might save some bandwidth by omitting them. Reference Scott Helme's: Micro-optimisation for fun!

Automatic Decompression

If you have a lot text-files compressed with gzip on disk and want the browser to decompress them on retrieval, you can use setenv to inject the Content-Encoding header, but should not do so unless all of your clients support Accept-Encoding: gzip. You should instead prefer mod_deflate, and pre-fill the mod_deflate cache location with compressed content.

  $HTTP["url"] =~ "(README|ChangeLog|\.txt)\.gz$" {
    setenv.set-response-header = ( "Content-Encoding" => "gzip")
    mimetype.assign = ("" => "text/plain" )
  }

Force Client Download

To tell clients to download a file rather than to try to display the response, set Content-Disposition.

$HTTP["url"] =~ "\.(dcf|m4a|mp3|mp4|wma|wmv|ogg|zip)$" {
    setenv.set-response-header = ("Content-Disposition" => "attachment")
}

To set the Content-Disposition header dynamically, e.g. with a specific filename=... parameter, see lua example in https://redmine.lighttpd.net/boards/2/topics/3127
Some examples in PHP can be found in the comments section at https://www.php.net/manual/en/function.header.php

Updated by gstrauss over 1 year ago · 30 revisions