Mod setenv » History » Revision 29
Revision 28 (da, 2022-08-10 11:53) → Revision 29/30 (gstrauss, 2022-08-10 21:12)
h1. Conditional Request Headers {{>toc}} *Module: mod_setenv* h2. 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 [[Docs_ModExpire|mod_expire]] instead of mod_setenv. To set "Content-Encoding", prefer [[Docs_ModDeflate|mod_deflate]]. Aside: arbitrarily complex header manipulation can be achieved using [[Docs_ModMagnet|mod_magnet]] (e.g. [[ModMagnetExamples#lua-mod_setenv|lua mod_setenv]]) if @mod_setenv@ config options below are not flexible enough for your needs. To use mod_setenv: @server.modules += ( "mod_setenv" )@ h2. Options *setenv.add-response-header* Adds a header to the HTTP response sent to the client: <pre>setenv.add-response-header = ("My-Custom-Header" => "my-custom-value")</pre> *setenv.add-request-header* Adds a header to the HTTP request that was received from the client: <pre>setenv.add-request-header = ("X-Proxy" => "my.server.name")</pre> *setenv.add-environment* Adds a value to the process environment (aka environment variables) that is passed to the external applications: <pre> setenv.add-environment = ( "TRAC_ENV" => "lighttpd", "RAILS_ENV" => "production" ) </pre> *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 for request or response header to remove request the header or remove response header. (there is no @remove-response-header@, just set an empty value). h2. 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. <pre> setenv.set-response-header += ("Strict-Transport-Security" => "max-age=31536000; includeSubDomains") </pre> See also [[Docs_ModSetEnv#Using-setenv-in-multiple-configuration-conditions|Using setenv in multiple configuration conditions]] below for setting HSTS along with other setenv directives. h2. Opt-out of Google's Federated Learning of Cohorts (FLoC) EFF article: "Google’s FLoC Is a Terrible Idea":https://www.eff.org/deeplinks/2021/03/googles-floc-terrible-idea <pre> setenv.set-response-header += ( "Permissions-Policy" => "interest-cohort=()" ) </pre> h2. Using setenv in multiple configuration conditions The way that lighttpd processes its [[Docs_Configuration|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" <pre> $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") } </pre> 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" <pre> $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") } </pre> 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" <pre> $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") } </pre> The following is one method to define a setenv set of policy headers into a variable and to reuse the set: <pre> 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" => "1; mode=block", "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") } </pre> 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. <pre> 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" => "1; mode=block", "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") } </pre> 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!":https://scotthelme.co.uk/micro-optimisation-for-fun/ h2. 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 [[Docs_ModDeflate|mod_deflate]], and pre-fill the mod_deflate cache location with compressed content. <pre> $HTTP["url"] =~ "(README|ChangeLog|\.txt)\.gz$" { setenv.set-response-header = ( "Content-Encoding" => "gzip") mimetype.assign = ("" => "text/plain" ) } </pre> h2. Force Client Download To tell clients to download a file rather than to try to display the response, set @Content-Disposition@. <pre> $HTTP["url"] =~ "\.(dcf|m4a|mp3|mp4|wma|wmv|ogg|zip)$" { setenv.set-response-header = ("Content-Disposition" => "attachment") } </pre> 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