Introduction
Running lighttpd2
You need two config files for lighttpd, which are usually in the following two locations:
-
/etc/lighttpd2/lighttpd.conf
: the main config, see Main Configuration. It can be split into multiple files, see Includes. -
/etc/lighttpd2/angel.conf
: the angel config, see Angel Configuration.
The contrib/
directory in the sources includes example config files.
Then start lighttpd2 with:
/usr/sbin/lighttpd2 -c /etc/lighttpd2/angel.conf
The process will not fork into background, you need to do that yourself if you want that.
Our recommended way to run lighttpd2 is runit (have a look at the contrib/service
directory).
Main Configuration
At the heart of each webserver is the configuration file. It controls the whole behaviour and therefore has to be mighty but at the same time easy enough to get the desired results without hassle.
In the lighttpd config you control how it reacts on requests. To achieve this you have to express some kind of logic – just like in a programming language.
Basic Syntax
The syntax for the lighttpd 2.0 configuration file is somewhat similar to various programming languages – kind of a mixture. But don’t be afraid, it is really simple. No pointers involved :)
The basic blocks are values, variables, function calls and conditions.
Values
Boolean
There are two boolean values:
true
false
Integers
Integers are stored as signed ints with 64 bits (max value around 9 * 10^18). There are three basic ways to use them:
- decimal integers: starting with any non zero digit, like
128
- octal integers: starting with a zero, like
0644
- hexadecimal integers: starting with
0x
like0xff
All three can have a suffix representing a factor the number gets multiplied with:
-
byte
:1
-
kbyte
:1024
-
mbyte
:1024*1024
-
gbyte
:1024*1024*1024
-
tbyte
:1024*1024*1024*1024
-
pbyte
:1024*1024*1024*1024*1024
-
bit
:1 / 8
-
kbit
:1024 / 8
-
mbit
:1024*1024 / 8
-
gbit
:1024*1024*1024 / 8
-
tbit
:1024*1024*1024*1024 / 8
-
pbit
:1024*1024*1024*1024*1024 / 8
-
sec
:1
-
min
:60
-
hours
:3600
-
days
:24*3600
For sizes the base unit is byte, and for intervals seconds.
Strings
There are 4 ways to specify a string:
'hello world'
"hello world"
e'hello world'
e"hello world"
The basic escape rules are the same for both:
-
"\n"
is a newline,"\r"
a carriage return,"\t"
a tab stop -
"\\"
is one\
,"\""
is one double quote"
and"\'"
a single quote'
- escaping single/double quote is optional if the symbol is not used to terminate the string, i.e.
'\"'
='"'
and"\'"
="'"
-
"\xNN"
: NN must be hexadecimal characters, and the string is replaced with the decoded 8-bit value as a single byte - All other
\
occurences are not removed from the string.
The e'..'
and e"..."
variant do not allow any occurences of the last kind to happen.
All other characters are allowed (the strings are parsed as 8-bit binary; lighttpd2 usually doesn’t care about the encoding).
Lists
Lists are ordered collections of other values:
-
[1, true, "foo"]
(simple list) -
[]
(empty list) -
[1]
(list with one element) -
[[1,2],[3,4],5]
(nested lists) -
[1,2,]
(final value can have a trailing,
too) -
(1, 2, 3)
(alternative brackets with more than one element)
Note that (1)
is not a list; parentheses can also be used to group expressions as in 1*(2+3)
, and it is therefore recommend to use only []
to specify lists.
Key-Value Lists
Key-value lists associates keys to values (both can be of any type); the syntax is:
[ "a" => 1, "b" => 10 ]
As with normal lists the final value can have a trailing ,
too. You cannot mix simple values and key-value associations in one list (nesting them works).
The key => value
operator is also only syntactic sugar for a [key, value]
list, the above example is the same as:
[ [a, 1], [b, 10] ]
This especially means that key-value lists are ordered too, although they can get converted to hash tables in certain function calls internally.
Expressions
There are operators available for some value types:
- for integers:
+
,-
,*
and/
- for strings:
+
(concatenate two strings) - for lists:
+
(append lists)
Also you can cast strings and booleans to integers and any value to strings:
-
cast(int) "256"
(only supports decimal representations and no suffix like above) -
cast(int) true
(true
maps to1
andfalse
to0
) cast(string) 5
Expressions can be grouped to override default association:
-
3 * (1 + 2)
vs.3 * 1 + 2
and so on
Action Blocks
An action block consists of a list of function calls (in action context) and conditionals; it is treated like a value, i.e. can be assigned to variables and used as parameter in function calls.
Action blocks can also contain variable assignments and setup blocks/function calls, which are not part of the “action block value” itself, but are evaluated while parsing the config.
The syntax is:
{
log "hello world";
if req.path =$ ".hidden" { static; }
}
The complete config is an action block too (but without the surrounding curly braces); conditionals also use action blocks for their branches.
Each action block that is not a condition branch starts a new nested variable scope;
Variables
Variable names start with a alphabetic character (a-z
and A-Z
) or an underscore _
, and are followed by alphanumeric characters, underscores _
and dots .
; keywords are not allowed as variable names.
Variables store values, and the name can be used in place of an actual value; later modifications to a variable have no influence on previous uses (i.e. are only evaluated while parsing the config).
Variables are assigned values with =
(and a terminating ;
)
my_types = [".txt" => "text/html"];
php = {
if phys.path =$ ".php" { fastcgi "unix:/var/run/lighttpd/php.sock"; }
};
By default variables assignment overwrites an existing variable (in its previous scope) or, if it doesn’t exist, creates a new one in the local scope (i.e. it will only be available in the current scope and nested descendants).
You can explicitly create a new variable in the local scope (hiding variables in parent scopes with the same name) by prefixing the assignment with local
:
local wwwpath = "/var/www/example.com";
You can also create variables in the global scope by prefixing the assignment with global
.
The main config already is in a nested scope (i.e. not the global scope). The global scope is not destroyed after config loading, and can be used in delayed config loading (say from SQL in the future).
If a variable name is used in a context it will always use the definition from the nearest scope.
Example
This example illustrates that variables are evaluated while parsing the config.
foo = "bar";
if req.path == "/somepath" {
foo = "baz";
}
# at this point foo will ALWAYS contain "baz" no matter if "/somepath" was requested or not
Example
This example illustrates scoping.
foo = "bar";
php = {
local foo = "baz";
# in this block (and nested blocks within) foo is now "baz"
# ...
};
# foo is now "bar" again
Special Variables
sys.*
variables are readonly. right now the following sys.*
variables are available:
-
sys.pid
: the process id of lighttpd -
sys.cwd
: the current working directory -
sys.env.X
: the system environment variable with nameX
(for anyX
)
Function calls
There are three types of function calls:
- actions
- setups
- options
Actions can only be used in action (block) context, setups only in setup (block) context, and options can be used in both.
A setup context is started with the keyword setup
, and is followed by either a single setup function call or a setup block.
Setup function calls are run immediately when they occur (they are used to “setup” the webserver environment, like listening on TCP sockets, and setting default options), while action function calls are run for each request (mapping request urls to physical paths, handling requests, modifying the default options).
The actions, setups and options are provided by the modules.
Includes
Includes are similar to function calls in the syntax, but are directly handled by the config parser. They are only allowed in action context, as they insert a reference to an action block at the point they are used.
There are three types of includes:
-
include "/etc/lighttpd/vhosts/*.conf";
: include files like the main config itself; the path can contain wildcards -
include_shell "/etc/lighttpd/config_generator.sh";
: runs the specified command, and parses the output of it as config file -
include_lua "/etc/lighttpd/complex.lua"
: includes a Lua config file. The single action to be executed must be returned in the globalactions
variable (or leave it empty to do nothing). See alsolua.handler
.
Includes also create a new nested scope.
Debug Print
Similar to includes __print
is a special function, but is available in action and setup context. It logs the string values of its parameter(s) with log level “debug”.
Conditions
Conditions (known from Lighttpd 1.x) are the equivalent to “if”s in most programming languages. There are also “else” and “elseif” equivalents.
They are created by a comparison of a condition variable and a value that is evaluated at runtime (for each request).
Conditions can be nested, and you can group them with and
and or
operators. and
binds stronger than or
, although it is preferred to group them with parentheses.
Example
if req.host == "mydomain.tld" {
if req.path == "/" or req.path == "/index.html" {
static;
} else if req.path == "/upload" {
if req.content_length > 100mbyte {
access.deny;
} else {
proxy "127.0.0.1:8080";
}
}
}
Syntax
The basic syntax forms are:
if <expr> { ... }
if <expr> { ... } else { ... }
if <expr> { ... } else if <expr2> { ... }
-
if <expr> { ... } else if <expr2> { ... } ...
(continue withelse
orelse if
)
A condition expression <expr>
is:
(<expr>)
<expr1> and <expr2>
-
<expr1> or <expr1>
(<expr1> and <expr2> or <expr3>
=(<expr1> and <expr2>) or <expr3>
;and
has higher precedence) -
<condvar> <op> <value>
for<condvar>
being a condition variable (with a type different from boolean),<op>
an condition operator and<value>
a string or a number. -
<condvar>
or!<condvar>
for a boolean condition variable.
Condition variables
There are three categories of condvars:
- request.xyz (can be abbreviated by req.xyz)
- physical.xyz (can be abbreviated by phys.xyz)
- response.xyz (can be abbreviated by resp.xyz)
variable | description |
---|---|
request.localip | ip address of the listing socket, the client connected to (filename for unix sockets) |
request.localport | port number of the listening socket, -1 for unix sockets |
request.remoteip | ip address of the client |
request.remoteport | port number of the client, -1 for unix sockets |
request.path | the path part of the requested url. not including the querystring. |
request.raw_path | the raw path (not urldecoded, not simplified) of the requested url, including the querystring. |
request.host | requested hostname |
request.scheme | scheme of the request. “http” or “https” |
request.query | the querystring of the requested url |
request.method | method of the request. “GET”, “POST”, “HEAD” etc. |
request.length | integer. length of the content for e.g. POST methods |
request.header[“name”] | request header name e.g. request.header[“referer”] |
request.is_handled | boolean condition, does request already have a handler (static, fastcgi..) |
request.environment[“name”] | (or short request.env[“name”]) CGI environment |
physical.path | physical path of the file to be served. e.g. document root + path |
physical.exists | boolean condition, indicates whether the requested file (normal file, directory or even a special file) exists |
physical.size | integer. size of the requested file. -1 if file doesn’t exist |
physical.is_dir | boolean condition, indicates whether the requested file is a directory |
physical.is_file | boolean condition, indicates whether the requested file is a normal file (e.g. no unix socket etc) |
physical.docroot | document root |
physical.pathinfo | pathinfo |
response.status | response status code (blocks request until response header is available) |
response.header[“name”] | response header (blocks request until response header is available) |
Condition operators
op | description | op | description |
---|---|---|---|
== | compares two values on equality | != | negative == |
<= | less than or equal | < | less than |
>= | greater than or equal | > | greater than |
=~ | regular expression match | !~ | negative =~ |
=^ | prefix match | !^ | negative =^ |
=$ | suffix match | !$ | negative =$ |
=/ | cidr match | !/ | negative =/ |
Angel Configuration
lighttpd2 consists of two main binaries: the angel (lighttpd2
) and the worker (lighttpd2-worker
). The main configuration is used by the worker, and this chapter describes the configuration for the angel.
A standard distribution should install a angel config in /etc/lighttpd2/angel.conf
with reasonable defaults which should work for most basic setups.
Angel concept
You can start the worker without the angel, but the angel provides some useful features:
- The angel itself usually runs as root (needed for example to bind to privileged ports), but will spawn the worker with dropped privileges (usually a user like
www-data
is used). The worker doesn’t do any privilege dropping itself. - The angel can open/create log files for the worker with root permissions
- The angel supports a graceful restart of the worker for config reloading: a new instance is spawned, and if it started successfully (checking config, …) it will replace the old instance. The old instance will finish the remaining requests.
As the angel is responsible for creating the listening network sockets, it can keep them open all the time and no request is lost. - The angel also does a simple supervise: if the worker crashes the angel will respawn it.
Config items
The config syntax is very similar to the main configuration, although it has no action blocks, setup blocks, conditionals and scopes.
user
drops privileges for spawning the worker
user username;
- username
- username to drop privileges to for spawning the worker
This item can only be specified once; if it is not specified it won’t drop privileges at all, which is useful if the angel itself doesn’t run as root. It should go without saying that you should never run the worker as root.
The username is also used to find all groups the user is in.
Example
user "www-data";
group
drops privileges for spawning the worker
group groupname;
- groupname
- groupname to drop privileges to for spawning the worker
Specify the main group to drop privileges to; a process can have multiple groups, and the others are given by the groups the user specified by user
is in.
The default is the main group of the user specified by user
, or not dropping privileges at all.
Example
group "www-data";
binary
specifies path to worker binary
binary path;
- path
- path to the lighttpd2-worker binary
This item should only be needed if you didn’t install the binaries at all (for testing).
Example
binary "/home/source/lighttpd2/autobuild/src/main/lighttpd2-worker";
config
specifies path to main config file
config path;
- path
- path to the main config file
By default /etc/lighttpd2/lighttpd.conf
is used.
Example
config "/etc/lighttpd2-test/lighttpd.conf";
luaconfig
specifies path to a lua config file
luaconfig path;
- path
- path to the lua config file
By default a normal config file is used; you must use either a normal config file or a lua config file.
Example
luaconfig "/etc/lighttpd2/lighttpd.lua";
modules_path
specifies path to directory containing modules for the worker
modules_path path;
- path
- path to the directory containing modules for the worker
This item should only be needed if you didn’t install the binaries at all (for testing). For autotool builds the “real” module binaries are in a .libs
subdirectory.
Example
modules_path "/home/source/lighttpd2/autobuild/src/modules/.libs";
wrapper
prefix worker command with other commands
wrapper wrappers;
- wrappers
- path to a wrapper command and its arguments
This item appends all given strings to the command prefix list (which starts as empty list). Before spawning the worker the binary path to the worker and its arguments (config, module path) are appended.
Wrappers can be used to run the worker with valgrind, strace and similar.
Example
# in multiple lines
wrapper [ "/usr/bin/valgrind" ];
wrapper [ "--leak-check=full", "--show-reachable=yes" ]
wrapper [ "--leak-resolution=high" ];
# or as one
wrapper [ "/usr/bin/valgrind", "--leak-check=full", "--show-reachable=yes", "--leak-resolution=high" ];
env
add environment variables for the worker
env vars;
- vars
- list of environment variables to add for the worker to run with
Append the given list of environment variables (starts empty), which can be either strings of the form "var=xyz"
or key-value pairs "var" => "xyz"
(the keys must not contain any =
).
Example
# helps debugging with valgrind:
env [ "G_SLICE=always-malloc", "G_DEBUG=gc-friendly,fatal_criticals" ];
copy_env
copies environment variables for the worker from current environment
copy_env varnames;
- varnames
- list of environment variable names to copy
Adds copies of variables from the current environment. By default all variables will be dropped.
Example
env_copy [ "PATH" ];
max_core_file_size
sets limit of core file size for the worker
max_core_file_size limit;
- limit
- limit in bytes
Maximum size of a core file, in bytes, that may be created by the worker. Core files are created when the worker crashes.
0 disables core files, and by default the limit is not changed.
max_open_files
sets limit of maximum open file for the worker
max_open_files limit;
- limit
- maximum number of open files
The worker limits the maximum number of connection based on the maximum number of open files (max connections = max open files / 4).
By default the limit is not changed.
Example
# max 4096 connections
max_open_files 16384;
allow_listen
allow worker to listen on sockets
allow_listen list;
- list
- list of network mask (CIDR) + optional port or unix domain socket addresses
The worker uses the angel to bind TCP/unix sockets; the angel checks whether those binds are allowed. If no allow_listen
is specified, all TCP binds (IPv4 and IPv6) using port 80 or 443 are allowed.
IPv4 and IPv6 use different masks (no IPv4 to IPv6 mapping), the network length for the CIDR mask is optional (defaults to a host address), and the port is optional too (allowing both 80 and 443 if omitted).
Formats:
- TCP on IPv4:
ipv4
,ipv4:port
,ipv4/net
,ipv4/net:port
- TCP on IPv6:
ipv6
,ipv6/net
,[ipv6]
,[ipv6/net]
,[ipv6]:port
,[ipv6/net]:port
- Unix domain:
unix:/wildcard/path/to/*.socket
Example
Only allow TCP port 8080 for IPv4 and IPv6 and unix domain socket /run/lighttpd/internal.sock
.
allow_listen [ "0.0.0.0/0:8080", "[::/0]:8080" ];
allow_listen "unix:/run/lighttpd/internal.sock";
Patterns
The lighttpd config supports “patterns” in various places (docroot, redirect, rewrite, env.set, …); and they share the following structure.
There are two kinds of “captures” available; one from the action itself (like captures from a regular expression in redirect/rewrite, or the labels from the hostname in docroot), and the captures from the previous matching regular expression conditional in the action stack. If there was no capture of the selected kind the values will be empty strings.
Syntax
A pattern is a string consisting of the following parts:
- simple text. can contain special characters $ and % only when they are escaped with
\
– remember, that the\
has to be escaped too for the config, so you’ll probably have to use\\
to escape. You are allowed to escape?
too (used for special “split” in rewrite). - ”%” capture references (previous matching regular expression conditional); either followed by a single digit, or a range (see below for range syntax)
- ”$” capture references (depends on action); either followed by a single digit, or a range (see below for range syntax)
- ”%” references to condition variables, for example:
%{req.path}
; the conditional can be prefixed with “enc:” (%{enc:req.path}
), in which case the value will be urlencoded.
Ranges
Ranges can either be a single element [n]
(n
can have more than one digit), a closed range [n-m]
or an open range [n-]
or [-m]
;
the open end is always replaced with “G_MAXUINT” (a very big positive integer). ranges can be “reversed”, i.e. n > m
.
There are now two different ways ranges are used:
- ranges of regular expression captures: the captures are just inserted for all values in the range; if the range is reversed, it starts with the highest index in the range.
- ranges of labels in a hostname: similar to the first range, but the inserted labels are separated by a “.”; the index 0 stands for “complete hostname”, and ranges including 0 are reduced to the complete hostname; the labels are numbered from top-level, and the range is interpreted reversed (just have a look at the examples, and it will be clear).
Example: simple redirect
redirect "http://%{req.host}%{req.path}?redirected=1";
Example: docroot
- a request to “http://example.com/project/trunk” would lead to docroot “/var/www/project/trunk/htdocs”
- a request to “http://sub.example.com/” would lead to docroot “/var/www/vhosts/com/sub.example”
if req.path =~ "^/project/([^/]*)" {
docroot "/var/www/projects/%1/htdocs";
} else {
docroot "/var/www/vhosts/$1/${2-}/htdocs";
}
Regular expressions
lighttpd2 uses the “Perl-compatible regular expressions” implementation from GLib, see their Regular expression syntax documentation.
The config format has different ways to provide strings (you can quote with either '
or "
; the character used to quote has to be escaped with \
if used inside the string).
The simple (standard) way "text"
has the following escape rules:
-
"\n"
is a newline,"\r"
a carriage return,"\t"
a tab stop -
"\\"
is one\
,"\""
is one double quote"
and"\'"
a single quote'
- escaping single/double quote is optional if the symbol is not used to terminate the string, i.e.
'\"'
='"'
and"\'"
="'"
-
"\xNN"
: NN must be hexadecimal characters, and the string is replaced with the decoded 8-bit value as a single byte - All other
\
occurences are not removed from the string.
This way is the preferred one for regular expressions; only to actually match a \
you have to do additional escaping ("\\\\"
; "\x5C"
= "\\"
is not working), and \\
is usually not doing what you wanted (matching a digit: "\\d"
= "\d"
). All other escape rules are compatible with what pcre is doing.
The second way is to place an e
before the string like this: e"text"
. It has the same rules like the normal string, but does not allow unknown escape sequences (the last rule above).
To match a digit with pcre this way you’d have to write e"\\d"
instead of "\d"
.
Fetch API
The Fetch API provides a common interface between lighttpd modules to lookup entries in a database. Both lookup key and data are simple (binary) strings.
So far only a fetch.files_static is providing a database, and only gnutls is using it to lookup SNI certificates.
Lua API
Lua can be used to generate configs (like a shortcut to include_shell
) or to write actual response handlers.
Using Lua to generate configs doesn’t have any performance impact; in this case Lua is only run at startup to generate the config, and there is no Lua involved for processing requests.
As a lua_State
itself is not thread-safe, you have two ways to use Lua configs:
-
include_lua
andlua.plugin
: using a global server lock, but with sharing the samelua_State
in all workers -
lua_handler
: without locking, and every worker has its ownlua_State
(and they cannot share their global context).
Lua Config
This section describe how to translate concepts from the main config to Lua. You can write the whole config in Lua or only parts and include them (for example with include_lua
).
Example - debug.lua
The following Lua snippet saved as “debug.lua” could for example be included with include_lua "debug.lua"
.
function mydebug(vr)
local url_fields = { "raw", "raw_path", "raw_orig_path", "scheme", "authority", "path", "query", "host" }
local phys_fields = { "path", "doc_root", "pathinfo" }
if vr:handle_direct() then
vr.resp.status = 200
vr.resp.headers["Content-Type"] = "text/plain"
vr.out:add("Hello World!\n\n")
vr.out:add("http method: " .. vr.req.http_method .. "\n")
vr.out:add("http version: " .. vr.req.http_version .. "\n")
for k, v in vr.env:pairs() do
vr.out:add("Env['" .. k .. "'] = '" .. v .. "'\n")
end
vr.out:add("\n")
for k, v in pairs(url_fields) do
vr.out:add("vr.req.uri['" .. v .. "'] = '" .. vr.req.uri[v] .. "'\n")
end
vr.out:add("\n")
for k, v in pairs(phys_fields) do
vr.out:add("vr.phys['" .. v .. "'] = '" .. vr.phys[v] .. "'\n")
end
vr.out:add("\n")
for k, v in vr.req.headers:pairs() do
vr.out:add("vr.req.headers['" .. k .. "'] = '" .. v .. "'\n")
end
end
end
actions = mydebug
Values
- Boolean: Lua supports
true
andfalse
directly - Integers: Lua has its own number type (usually a
double
), and doesn’t know any of the suffixes. - Strings: Lua supports strings directly. Check the Lua reference for the various quoting styles.
- Lists and Key-Value-Lists: Lua has a “table” type; it can contain sequential lists and associative mappings. Use
{1, 2, 3}
to create simple lists,{a=1, b=2}
to create unique mappings (which get converted to Key-Value-Lists) or{{"a",1},{"a",2}}
to explicitly create Key-Value-Lists (where a key can be used more than once and the order matters).
Don’t mix sequential lists and associative mappings.
If you get a List (possible a Key-Value-List) value from lighttpd it is represented as sequential list but has a special__index
meta-table method supporting strings andnil
as lookup parameter, i.e. you can treat a Key-Value-List like an associative mapping in Lua (see for example the options handling in contrib/secdownload.lua). - Expressions and variables just are the usual Lua things; there is no direct access to the lighttpd config variables (yet).
- Action blocks: you can make an action from a list of actions using the list action (
act = action.list(act1, act2)
)
Function calls
Action context is given by prefixing the function name with action.
, and setup context by prefixing with setup.
. Don’t try to call setups in request handling.
Also each Lua function can act as an action (see the debug.lua example above), taking a virtual request object as parameter.
Includes are not supported, neither is the debug __print
(there are other logging methods available).
Conditions
Conditions are the ugliest part: there is no way translating native Lua if statements into the lighttpd config, so they need to be constructed manually.
Only the long names of the condition variables are available in Lua. The condition operators are all given names and appended to the condition variable, and then called with the value to compare with.
op | Lua name | op | Lua name |
---|---|---|---|
== | :eq |
!= | :ne |
<= | :le |
< | :lt |
>= | :ge |
> | :gt |
=~ | :match |
!~ | :nomatch |
=^ | :prefix |
!^ | :notprefix |
=$ | :suffix |
!$ | :notsuffix |
=/ | :ip |
!/ | :notip |
Boolean condition variables are called with :is()
or :isnot()
.
The result of such call (a “condition”) is then passed as first parameter to action.when
.
Example - admin only
Translating if req.env["REMOTE_USER"] != "admin" { auth.deny; }
to Lua:
actions = action.when(request.environment["REMOTE_USER"]:ne("admin"), action.auth.deny())
Example - physical files only
Translating if !phys.exists { auth.deny; }
to Lua:
actions = action.when(physical.exists:isnot(), action.auth.deny())
API
This section documents the object types you need to handle requests; you will probably start from the Virtual Request object you get as parameter in your handler.
Object fields should be accessed with .field
or ["field"]
, for example:
e = vr.env
e["XXX"] = "abc"
Fields tagged with (ro) are read only; that does not mean the fields value can’t be modified, you only cannot overwrite the field with another object. Readonly string / number properties are really read only though.
Call object methods with :method(...)
:
vr:print("Hello World")
Note:
The obj:method(par1, par2, ...)
syntax is just another way to say obj["method"](obj, par1, par2, ...)
(but obj
is only evaluated once), so field and method names live in the same namespace.
This means that our container types cannot provide access to fields which have the same names as the methods (and the methods starting with “__” are not listed here), so you have to use explicit access methods to read generic fields in such containers (write is not a problem as we don’t allow writing methods).
All container types should provide a get
and a set
method to provide “clean” access to the container contents.
pairs()
Some objects may provide a :pairs()
method to loop through the fields (not the methods); this works for simple things like
for k, v in vr.env:pairs() do
vr:print("env['" .. k .. "'] = '" .. v .. "'")
end
lua expects that the :pairs
method returns a next, obj, startkey
tuple and loops through the list with k = startkey; while k, v = next(obj, k) do ... end
; but the next()
method is supposed to use k
as previous key and to return the next one.
Our next
methods will keep the current position in an internal object (associated with the next
function as upvalue), and will advance on every call ignoring the obj
and k
parameter.
Global constants
liHandlerResult
enumeration values:
lighty.HANDLER_GO_ON
lighty.HANDLER_COMEBACK
lighty.HANDLER_WAIT_FOR_EVENT
lighty.HANDLER_ERROR
Global methods
-
lighty.print
(andlighty.error
andprint
): print parameters via lua “tostring” method as ERROR in global server context -
lighty.warning
: print parameters via lua “tostring” method as WARNING in global server context -
lighty.info
: print parameters via lua “tostring” method as INFO in global server context -
lighty.debug
: print parameters via lua “tostring” method as DEBUG in global server context -
lighty.filter_in(class)
: creates a new action, which adds a incoming filter fromclass:new(vr)
if called at runtime -
lighty.filter_out(class)
: creates a new action, which adds a outgoing filter fromclass:new(vr)
if called at runtime -
lighty.md5(str)
: calculates the md5 checksum of the stringstr
(returns the digest as string in hexadecimal) -
lighty.sha1(str)
: calculates the sha1 checksum of the stringstr
(returns the digest as string in hexadecimal) -
lighty.sha256(str)
: calculates the sha256 checksum of the stringstr
(returns the digest as string in hexadecimal) -
lighty.path_simplify(str)
: return simplified path
Example
lighty.print("Hello World!")
Example
local MyFilterclass = { }
MyFilterClass.__index = MyFilterClass
function MyFilterClass:new(vr)
local o = { }
setmetatable(o, self)
return o -- return nil if you want to skip the filter this time
end
function MyFilterClass:handle(vr, outq, inq) ... end
actions = lighty.filter_out(MyFilterClass)
Virtual Request
Fields:
-
con
(ro): Connection -
in
(ro): Chunk Queue, read request post content -
out
(ro): Chunk Queue, write response content -
env
(ro): Environment, (fast)cgi environment -
req
(ro): Request, data from request header -
resp
(ro): Response, response header data -
phys
(ro): Physical, paths and filenames -
is_handled
(ro): whether vrequest is already handled -
has_response
(ro): whether the response headers (and status) is available
Methods:
-
print(...)
: print parameters via luatostring
method as ERROR in Virtual Request context -
warning(...)
: print parameters via luatostring
method as WARNING in Virtual Request context -
info(...)
: print parameters via luatostring
method as INFO in Virtual Request context -
debug(...)
: print parameters via luatostring
method as DEBUG in Virtual Request context -
handle_direct()
: handle vrequest (i.e. provide headers and body); returns true if not already handled. -
enter_action(act)
: push a new action on the action stack (return HANDLER_WAIT_FOR_EVENT to rerun after the pushed actions are done, HANDLER_GO_ON if you are done) -
st, res, errno, msg = stat(filename)
: async stat(filename). Following results are possible- st is the stat result, res == HANDLER_GO_ON, if the file was found. errno and msg are NIL. In all other cases st is NIL and res != HANDLER_GO_ON.
- res == HANDLER_WAIT_FOR_EVENT: stat() is in progress, just try again later (and return HANDLER_WAIT_FOR_EVENT in the meantime)
- res == HANDLER_ERROR: if stat() failed, errno contains the errno and msg the error message for the errno code.
-
add_filter_in(obj)
: addsobj
as lua incoming filter (needs to respond toobj:handle(vr, outq, inq)
and optionallyobj:finished()
); returns a Filter object -
add_filter_out(obj)
: addsobj
as lua outgoing filter (needs to respond toobj:handle(vr, outq, inq)
and optionallyobj:finished()
); returns a Filter object
Connection
-
local
: address of local socket -
remote
: address of remote host
Environment
Fields are the keys in the environment, so it behaves like a lua table; if you use keys starting with “__” or keys with the name of one of the methods below, you have to use the get
method to read them, for example:
x = env["set"] -- doesn't work, returns the set method instead
x = env:get("set") -- use this instead
x = env[y] -- don't do this, as y may be a special key like "set"
x = env:get(y) -- just do it the safe way if you are not sure
Methods:
-
get(k)
: safe way forenv[k]
-
set(k, v)
: safe way forenv[k] = v
-
unset(k)
: safe way forenv[k] = nil
-
weak_set(k, v)
: don’t override old value, safe way forenv[k] = env[k] or v
-
pairs()
: use to loop through keys:for k, v in env:pairs() do ... end
-
clear()
: remove all entries
Chunk Queue
Fields:
-
is_closed
: whether the ChunkQueue is closed
Methods:
-
add(s)
: appends a string to the queue -
add({filename="/..."})
: appends a file to the queue (only regular files allowed) -
reset()
: removes all chunks, resets counters -
steal_all(from)
: steal all chunks from another queue (useful in a filter if you decide to pass all data through it) -
skip_all()
: skips all chunks (removes all chunks but does not reset counters)
Request
Fields:
-
headers
(ro): HTTP Headers -
http_method
(ro): HTTP method string (“GET”, “POST”, “HEAD”, …) -
http_version
(ro): HTTP version string (“HTTP/1.0”, “HTTP/1.1”) -
content_length
(ro): Numeric value of Content-Length header (not updated automatically if someone changes the header value), -1 if not specified -
uri
: Request URI
Request URI
Fields:
-
raw
: Request uri as it was in the HTTP Request Line (or a rewrite result) -
raw_path
: not decoded path with querystring (will be the same asraw
for most requests, unless someone does something likeGET http://example.com/test?abc HTTP/1.1
) -
raw_orig_path
: same as raw_path, but saved before any rewrite happened -
scheme
: “http” or “https” -
authority
: complete host name header (or authority in an absolute url), e.g. “user@www.example.com.:8080” -
path
: decoded and simplified path name, without authority, scheme, query-string; e.g. “/index.php” -
host
: simple hostname, without auth information, without port, without trailing dot; e.g. “www.example.com” -
query
: The querystring, e.g. “a=1&b=2”
Response
Fields:
-
headers
(ro): HTTP Headers -
status
: HTTP status code
Physical
Fields:
-
path
: physical path -
doc_root
: document root -
pathinfo
: pathinfo
HTTP Headers
Same restriction as Environment for fields.
Methods:
-
get(k)
: joins all header values for the keyk
with “, “ (as the rfc allows it) -
set(k, v)
: removes all headers with keyk
and, if v is not nil, appends new “k: v” header -
append(k, v)
: appends “, v” to last header value with key k if it already exists,insert(k, v)
otherwise -
insert(k, v)
: appends new “k: v” header to list -
unset(k)
: removes all headers with keyk
-
pairs()
: loops through all headers. Please note that the keys are not unique! -
list(k)
: loops through all headers with keyk
-
clear()
: remove all headers
Filter
Represents a “liFilter”.
Fields:
-
in
(ro): Chunk Queue, incoming stream -
out
(ro): Chunk Queue, outgoing stream
Stat struct
Represents “struct stat”. Most fields should be self explaining (man 2 stat
(debian manpage) if you don’t know them).
Fields:
-
is_file
(ro): S_ISREG(mode) -
is_dir
(ro): S_ISDIR(mode) -
is_char
(ro): S_ISCHR(mode) -
is_block
(ro): S_ISBLK(mode) -
is_socket
(ro): S_ISSOCK(mode) -
is_link
(ro): S_ISLNK(mode) -
is_fifo
(ro): S_ISFIFO(mode) -
mode
(ro) -
mtime
(ro) -
ctime
(ro) -
atime
(ro) -
uid
(ro) -
gid
(ro) -
size
(ro) -
ino
(ro) -
dev
(ro)
Module index
Modules
name | description |
---|---|
plugin_core | contains the core features for generic request handling, static files, log files and buffer limits. |
mod_access | lets you filter clients by IP address. |
mod_accesslog | logs requests handled by lighttpd to files, pipes or syslog. The format of the logs can be customized using printf-style placeholders. |
mod_auth | requires authentication from clients using a username and password. It supports the basic (digest not yet) authentication method as well as plaintext, htpasswd and htdigest backends. |
mod_balance | balances between different backends. |
mod_cache_disk_etag | caches generated content on disk if an etag response header is set; if the backend sends an already cached etag, the backend is closed and the file is sent directly. |
mod_debug | offers various utilities to aid you debug a problem. |
mod_deflate | mod_deflate compresses content on the fly |
mod_dirlist | lists files inside a directory. The output can be customized in various ways from style via css to excluding certain entries. |
mod_expire | add "Expires" and "Cache-Control" headers to the response |
mod_fastcgi | connect to FastCGI backends for generating response content |
mod_flv | provides flash pseudo streaming |
mod_fortune | loads quotes (aka fortune cookies) from a file and provides actions to add a random quote as response header (X-fortune) or display it as a page. |
mod_gnutls | listens on separate sockets for TLS connections (https) using GnuTLS |
mod_limit | limits concurrent connections or requests per second. |
mod_lua | load lua plugins and actions |
mod_core (lua) | provides some useful helpers written in lua |
mod_secdownload (lua) | protects files with a time limited code |
mod_memcached | caches content on memcached servers |
mod_openssl | listens on separate sockets for TLS connections (https) using OpenSSL |
mod_progress | track connection progress (state) via a unique identifier |
mod_proxy | connect to HTTP backends for generating response content |
mod_redirect | redirect clients by sending a http status code 301 plus Location header |
mod_rewrite | modifies request path and querystring |
mod_scgi | connect to SCGI backends for generating response content |
mod_status | displays a page with internal statistics like amount of requests (total or per second), active connections etc. |
mod_throttle | limits outgoing bandwidth usage |
mod_userdir | allows you to have user-specific document roots being accessed through http://domain/~user/ |
mod_vhost | offers various ways to implement virtual webhosts |
Actions
name | module | description |
---|---|---|
access.check | mod_access | allows or denies access based on client IP address |
access.deny | mod_access | denies access by returning a 403 status code |
alias | plugin_core | sets doc-root depending on a matching prefix |
auth.deny | mod_auth | handles request with "401 Unauthorized" |
auth.htdigest | mod_auth | requires authentication using a htdigest file |
auth.htpasswd | mod_auth | requires authentication using a htpasswd file |
auth.plain | mod_auth | requires authentication using a plaintext file |
auth.require_user | mod_core (lua) | require a specific authenticated user |
balance.rr | mod_balance | balance between actions (list or single action) with Round-Robin |
balance.sqf | mod_balance | balance between actions (list or single action) with SQF |
cache.disk.etag | mod_cache_disk_etag | cache responses based on the ETag response header |
core.cached_html | mod_core (lua) | try to find a file for the current url with ".html" suffix, if we couldn't find a static file for the url yet and the url doesn't already have the ".html" suffix. |
core.wsgi | mod_core (lua) | Splits the url into SCRIPT_NAME (the subdirectory a web application is mounted at) and PATH_INFO (the path the web application should route) |
core.xsendfile | mod_core (lua) | provides a simple X-Sendfile feature; send a "X-Sendfile: /path/to/file" response header from your backend |
debug.profiler_dump | mod_debug | dumps all allocated memory to the profiler output file if profiling enabled |
debug.show_connections | mod_debug | shows a page similar to the one from mod_status, listing all active connections |
debug.show_events | mod_debug | shows a plain text list of all events |
deflate | mod_deflate | waits for response headers; if the response can be compressed deflate adds a filter to compress it |
dirlist | mod_dirlist | lists file in a directory |
docroot | plugin_core | sets doc-root, and builds physical path for requested file |
env.add | plugin_core | sets a connection environment variable if not already set |
env.clear | plugin_core | removes all connection environment variables |
env.remove | plugin_core | removes a connection environment variable |
env.set | plugin_core | sets a connection environment variable |
expire | mod_expire | adds an "Expires" header to the response |
fastcgi | mod_fastcgi | connect to FastCGI backend |
flv | mod_flv | pseudo stream the current file as flash |
fortune.header | mod_fortune | adds a random quote as response header "X-fortune". |
fortune.page | mod_fortune | shows a random cookie as response text |
header.add | plugin_core | adds a new response header line |
header.append | plugin_core | appends value to response header line |
header.overwrite | plugin_core | overwrite response header line or add new one |
header.remove | plugin_core | remove existing response header |
index | plugin_core | default filenames to show in a directory |
io.buffer_in | plugin_core | set memory limit for incoming chunkqueues (default is 256KiB) |
io.buffer_out | plugin_core | set memory limit for outgoing chunkqueues (default is 256KiB) |
io.throttle | mod_throttle | set the outgoing throttle limits for current connection |
io.throttle_ip | mod_throttle | adds the current connection to an IP-based throttle pool for outgoing limits |
io.throttle_pool | mod_throttle | adds the current connection to a throttle pool for outgoing limits |
limit.con | mod_limit | limits the total amount of concurrent connections to the specified limit. |
limit.con_ip | mod_limit | limits the total amount of concurrent connections per IP to the specified limit. |
limit.req | mod_limit | limits the amount of requests per second to the specified limit. |
limit.req_ip | mod_limit | limits the amount of requests per second per IP to the specified limit. |
list | plugin_core | (lua) combines a list of actions into one action, only needed in lua |
log | plugin_core | overwrite log targets for all log levels |
log.write | plugin_core | writes a log message to the "info" log level |
lua.handler | mod_lua | load file as lua config |
map | plugin_core | maps the result of a pattern to a user defined action |
memcached.lookup | mod_memcached | searches the content in a memcached database |
memcached.store | mod_memcached | stores the generated response in a memcached database |
openssl.setenv | mod_openssl | set SSL environment strings |
pathinfo | plugin_core | splits physical path into existing file/directory and the remaining PATH_INFO |
progress.show | mod_progress | returns state information about the request tracked with the ID specified by the X-Progress-ID querystring parameter |
progress.track | mod_progress | tracks the current request if the X-Progress-ID querystring key is supplied |
proxy | mod_proxy | connect to HTTP backend |
redirect | mod_redirect | redirect clients |
req_header.add | plugin_core | adds a new request header line |
req_header.append | plugin_core | appends value to request header line |
req_header.overwrite | plugin_core | overwrite request header line or add new one |
req_header.remove | plugin_core | remove existing request header |
respond | plugin_core | returns a quick response with optional body |
rewrite | mod_rewrite | modify request path and querystring |
rewrite_raw | mod_rewrite | modify request path and querystring, matching and writing raw path |
scgi | mod_scgi | connect to SCGI backend |
secdownload | mod_secdownload (lua) | protect files with a time limited code |
set_status | plugin_core | modify HTTP status code |
static | plugin_core | handle GET and HEAD requests with a static file from disk |
static_no_fail | plugin_core | handle GET and HEAD requests with a static file from disk |
status.info | mod_status | returns the status page to the client |
userdir | mod_userdir | builds the document root by replacing certain placeholders in path with (parts of) the username. |
vhost.map | mod_vhost | maps given hostnames to action blocks |
vhost.map_regex | mod_vhost | maps matching hostname patterns to action blocks |
when | plugin_core | (lua) build a conditional block (only usable in lua) |
Setups
name | module | description |
---|---|---|
debug.show_events_after_shutdown | mod_debug | time in seconds after start of shutdown to log remaining active events |
fetch.files_static | plugin_core | starts a Fetch API provider |
fortune.load | mod_fortune | loads cookies from a file, can be called multiple times to load data from multiple files |
gnutls | mod_gnutls | setup a TLS socket |
io.timeout | plugin_core | sets the global I/O timeout (wait for network read and write) |
listen | plugin_core | listen to a socket address, see above for accepted formats (default TCP port is 80) |
log | plugin_core | sets default log targets for all log levels |
log.timestamp | plugin_core | sets the format string to use for timestamps in the log |
lua.plugin | mod_lua | load file as lua plugin |
module_load | plugin_core | load the given module(s) |
openssl | mod_openssl | setup a TLS socket |
progress.ttl | mod_progress | Time to live in seconds for entries after a disconnect in the internal lookup table |
stat_cache.ttl | plugin_core | set TTL for stat cache entries |
tasklet_pool.threads | plugin_core | sets number of background threads for blocking tasks |
workers | plugin_core | sets worker count; each worker runs in its own thread and works on the connections it gets assigned from the master worker |
workers.cpu_affinity | plugin_core | binds worker threads to a cpu, only available on Linux systems |
Options
name | module | description |
---|---|---|
access.log_blocked | mod_access | whether to log when access was denied (with log level "info") |
access.redirect_url | mod_access | url to redirect to if access was denied (not implemented yet) |
accesslog | mod_accesslog | defines the log target |
accesslog.format | mod_accesslog | defines the log format |
auth.debug | mod_auth | enable debug output |
balance.debug | mod_balance | enable debug output |
buffer_request_body | plugin_core | enable buffering request body on disk |
debug.log_request_handling | plugin_core | enable debug output for request handling |
deflate.debug | mod_deflate | enable debug output |
etag.use | plugin_core | list of properties used to calculate etag; specify empty list to disable etags. Available: "inode", "mtime", "size" |
fastcgi.log_plain_errors | mod_fastcgi | whether to prepend timestamp and other info to FastCGI stderr lines in the "backend" log |
keepalive.requests | plugin_core | maximum number of requests a client is allowed to make in one connection |
keepalive.timeout | plugin_core | how long a keep-alive connection is kept open (in seconds) |
mime_types | plugin_core | maps file extensions to MIME types |
progress.debug | mod_progress | enable debug output |
progress.methods | mod_progress | Request methods to track |
redirect.debug | mod_redirect | enable debug output |
rewrite.debug | mod_rewrite | enable debug output |
server.name | plugin_core | server name; is used in some places instead of the HTTP request hostname if the latter was not specified in the (HTTP/1.0) request |
server.tag | plugin_core | used to display server name + version in different places (HTTP response header, CGI environment, mod_dirlist footer, ...) |
stat.async | plugin_core | enables async stat() calls |
static.exclude_extensions | plugin_core | don't deliver static files with one of the listed extensions |
static.range_requests | plugin_core | enabled ranged requests |
status.css | mod_status | defines the stylesheet to use. available: unset (default), "blue" or any url you wish |
strict.post_content_length | plugin_core | require Content-Length for POST requests |
vhost.debug | mod_vhost | enable debug output |
plugin_core
plugin_core contains the core features for generic request handling, static files, log files and buffer limits.
Socket addresses
The following address formats can be used:
IPv4
Either with port 192.168.0.1:80
or without 192.168.0.1
; you can either use real IPs or 0.0.0.0
to listen on all network interfaces.
IPv6
Similar to IPv4; just put the IPv6 between “[” and “]” like this: [::1]:80
(IPv6 localhost with port 80).
Please note that lighttpd always listens to IPv6 only (some platforms listen to IPv4 too on [::] by default).
Unix domain sockets
A unix domain socket needs a filename where the socket is placed; use unix:/path/to/socket
as socket address.
Please don’t put unix domain sockets in /tmp
. Use /var/run/lighttpd/
or something like that, where only root or selected “trusted” users can create files.
This may be not supported in all places where a socket address can be specified.
Options
debug.log_request_handling (option)
enable debug output for request handling
debug.log_request_handling value;
Example
debug.log_request_handling true;
static.range_requests (option)
enabled ranged requests
static.range_requests value;
Example
static.range_requests false;
keepalive.timeout (option)
how long a keep-alive connection is kept open (in seconds)
keepalive.timeout timeout;
Example
keepalive.timeout 30;
keepalive.requests (option)
maximum number of requests a client is allowed to make in one connection
keepalive.requests requests;
Example
keepalive.requests 10;
etag.use (option)
list of properties used to calculate etag; specify empty list to disable etags. Available: "inode", "mtime", "size"
etag.use properties;
Example
etag.use ();
stat.async (option)
enables async stat() calls
stat.async value;
If a filename is in lighttpd’s stat “cache”, lighttpd assumes the kernel still has the entry in memory, and stat()
therefore is unlikely to block.
Otherwise it will ask a background thread to call stat()
, so the main worker threads are not waiting on a slow disk (or a network filesystem), but only if stat.async
is enabled.
If you know your disk is fast enough (perhaps a ramdisk?) and want to save the context switch to the background thread you can disable this.
buffer_request_body (option)
enable buffering request body on disk
buffer_request_body value;
Some backends like to wait for the complete response before forwarding/handling it. For this they require this option to save some memory.
strict.post_content_length (option)
require Content-Length for POST requests
strict.post_content_length value;
Some clients don’t send Content-Length for POST requests with empty body; they should send Content-Length: 0
. When this check is enabled they’ll get a 411 Length required
error.
static.exclude_extensions (option)
don't deliver static files with one of the listed extensions
static.exclude_extensions extensions;
Example
static.exclude_extensions [ ".php", ".htaccess", ".htpasswd" ];
server.name (option)
server name; is used in some places instead of the HTTP request hostname if the latter was not specified in the (HTTP/1.0) request
server.name hostname;
Even HTTP/1.0 clients usually specify a Host: header; without Host: header you could only run one domain on one IP address.
This option is for the rare case that you want to handle clients without Host: header support in a nice way.
Example
server.name "lighttpd.net";
server.tag (option)
used to display server name + version in different places (HTTP response header, CGI environment, mod_dirlist footer, ...)
server.tag tag;
The default is “lighttpd/” + the current version.
mime_types (option)
maps file extensions to MIME types
mime_types mapping;
Default MIME type is “application/octet-stream”. The sources contain a mimetypes example config with many standard mappings.
The longest matching suffix is used (".tar.gz"
always wins over ".gz"
), and in case of duplicate entries the last one is used.
Example
mime_types [ ".htm" => "text/html", ".txt" => "text/plain; charset=utf-8" ];
Actions needed from lua
These action are not needed (or usable) in non-lua configs.
list (action)
(lua) combines a list of actions into one action, only needed in lua
list actions;
- actions
- list of actions to combine
when (action)
(lua) build a conditional block (only usable in lua)
when (condition, action1, action2);
- condition
- A condition; can only be constructed in lua
- action1
- action to run if condition was true or lua "nil"
- action2
- (optional) action to run if condition was false
Mapping URL paths to filenames
docroot (action)
sets doc-root, and builds physical path for requested file
docroot patterns;
- patterns
- One or more patterns to build docroot from
Uses patterns to build document roots (base location of files to server).
docroot
uses the first pattern that results in an existing directory; otherwise it uses the last entry.
You’ll want the docroot
action before alias
actions!
Example
docroot ("/var/www/vhosts/$0/htdocs", "/var/www/default/htdocs");
alias (action)
sets doc-root depending on a matching prefix
alias mapping;
- mapping
- maps prefix to base location on disk
The prefix is removed from the URL path before it is appended to the base location.
You’ll want the docroot
action before alias
actions!
Patterns are supported for alias targets as in docroot
. As only one pattern per prefix can be given alias
does not check whether the target exists.
Trailing slashes in the prefix used to indicate “directory handling” and get ignored for matching; “directory handling” is now always on.
That means URL paths only match at separator boundaries; the prefix /a
(and /a/
) matches the paths /a
, /a/
and /a/b
, but not /ab
.
Example
docroot ("/var/www/vhosts/$0/htdocs", "/var/www/default/htdocs");
alias [
"/phpmyadmin/" => "/usr/share/phpmyadmin",
"/pma/" => "/usr/share/phpmyadmin",
"/.well-known/openpgpkey/" => "/var/lib/gnupg/wks/$0/",
];
alias "/favicon.ico" => "/var/www/favicon.ico";
index (action)
default filenames to show in a directory
index filenames;
- filenames
- filenames to look for
If the physical path is a directory search for the specified filenames; prefix a filename with ‘/’ to search in the doc-root.
It works like this:
- if current physical path points to a regular file do nothing
- walk through the list of filenames to look for:
- if filename does not start with ‘/’ and the current physical path doesn’t point to a directory, ignore the entry
- if filename does not start with ‘/’ and the url didn’t end in a ‘/’, redirect request to url with ‘/’ appended
- if filename does not start with ‘/’ search for it in current physical path (which is a directory)
- if filename does start with ‘/’ search for it in the doc-root
Example
setup {
module_load "mod_dirlist";
}
# if a directory was requested, first search for some default files
index ["index.php", "index.html", "/index.php"];
# if none of them did exists show a simple directory listing
dirlist;
# ... + handle PHP and static files
pathinfo (action)
splits physical path into existing file/directory and the remaining PATH_INFO
pathinfo;
Searches for the longest prefix of the physical path name that exists, splitting only at the directory separator /
; also never leaves the document root (technically speaking the filename can’t get shorter than the document root).
Example
The following example maps http://example.com/index.php/some/site
to the file /var/www/index.php
with PATH_INFO=/some/site
(given /var/www/index.php
is a normal file).
docroot "/var/www";
pathinfo;
if phys.path =$ ".php" { fastcgi "unix:/var/run/lighttpd/php.sock"; }
Example
The following example maps http://example.com/some/site
to the file /var/www/index.php
with PATH_INFO=/some/site
(given /var/www/index.php
is a normal file, and /var/www/some
does not exist).
docroot "/var/www";
pathinfo;
index ("index.php");
if phys.path =$ ".php" { fastcgi "unix:/var/run/lighttpd/php.sock"; }
Generating responses
static (action)
handle GET and HEAD requests with a static file from disk
static;
This action is automatically appended to the global config (unless a lua config is specified at the command line).
Does nothing if:
- the request is already handled
- no physical path was set (missing
docroot
,alias
, …) - the physical path points to a directory
All other problems lead to an error page, for example:
- wrong request method (405)
- file not found (404)
- couldn’t open file (403)
- filename matches
static.exclude_extensions
(403) - …
static_no_fail (action)
handle GET and HEAD requests with a static file from disk
static_no_fail;
same as static
, but doesn’t return any error pages; instead request handling continues.
respond (action)
returns a quick response with optional body
respond (status, content);
- status
- HTTP response status code
- content
- (optional) pattern for response body
Generates a simple response (our favorite benchmark handler).
The body is parsed as pattern.
Example
respond 403 => "Forbidden";
Example
respond 200 => "benchmark content!";
Logging
Log levels
For standard logging (“error.log”) lighttpd knows the following levels:
debug
info
warning
error
-
abort
(right before terminating the process) -
backend
(for log data from backends, like FastCGI stderr stream)
Log targets
The following log targets are known:
- not logging: empty string
- files:
file:/var/log/error.log
or just/var/log/error.log
- stderr:
stderr:
orstderr
- syslog:
syslog:
(not supported yet) - pipes:
pipe:command
or| command
(not supported yet)
Unknown strings are mapped to stderr
.
log (action)
overwrite log targets for all log levels
log map;
- map
- mapping log levels (or default) to log targets
Example
log [
"error" => "/var/log/lighttpd/error.log",
"abort" => "/var/log/lighttpd/error.log",
"backend" => "/var/log/lighttpd/backend.log",
default => "/var/log/lighttpd/debug.log",
];
log.write (action)
writes a log message to the "info" log level
log.write message;
- message
- message pattern string
Writes the specified message to the log using level info
; the message is parsed as pattern.
Example
log.write "hello world";
log (setup)
sets default log targets for all log levels
log map;
- map
- mapping log levels (or default) to log targets
Example
setup {
log [
"error" => "/var/log/lighttpd/error.log",
"abort" => "/var/log/lighttpd/error.log",
"backend" => "/var/log/lighttpd/backend.log",
default => "/var/log/lighttpd/debug.log",
];
}
log.timestamp (setup)
sets the format string to use for timestamps in the log
log.timestamp format;
- format
- a strftime format string
See strftime for the format string syntax.
The default format string is "%d/%b/%Y %T %Z"
.
Connection environment
The connection environment is a set of variable with names and values (both simple strings). CGI backends will forward the environment in addition to the standard CGI environment variables.
The connection environment overwrites the standard CGI values.
env.set (action)
sets a connection environment variable
env.set (name, value);
- name
- the variable name to set
- value
- the pattern value to set
The value is parsed as pattern.
Example
env.set "INFO" => "%{req.path}";
env.add (action)
sets a connection environment variable if not already set
env.add (name, value);
- name
- the variable name to set
- value
- the pattern value to set
The value is parsed as pattern. env.add
does not overwrite already existing values.
Example
env.add "INFO" => "%{req.path}";
env.remove (action)
removes a connection environment variable
env.remove name;
- name
- the variable name to remove
Example
env.remove "INFO";
env.clear (action)
removes all connection environment variables
env.clear;
Example
env.clear;
Response header
All header values that get set are parsed as patterns.
header.add (action)
adds a new response header line
header.add (name, value);
- name
- header name
- value
- pattern header value
The HTTP spec requires that multiple headers with the same name could be merged by joining their values with “,”.
In real life this doesn’t work always, especially not for “Cookie” headers; so this action actually adds a separate header line.
Example
header.add "Cache-Control" => "public";
header.append (action)
appends value to response header line
header.append (name, value);
- name
- header name
- value
- pattern header value
If header already exists appends new value separated by “, “; otherwise adds a new header line.
header.overwrite (action)
overwrite response header line or add new one
header.overwrite (name, value);
- name
- header name
- value
- pattern header value
If header already exists overwrites the value; otherwise a new line gets added.
header.remove (action)
remove existing response header
header.remove name;
- name
- header name
Example
# ... some PHP handling
# wait for response headers to be ready
if resp.status >= 0 {
header.remove "X-Powered-By";
}
set_status (action)
modify HTTP status code
set_status;
Modifies the HTTP status code, but doesn’t handle the request in any way.
Later actions could overwrite the status, or a backend (FastCGI, proxy, …) might overwrite it if the response is parsed later.
Only works if some action actually handled the request.
Lighttpd will generate error pages (if it knows the code) if the action that handled the request didn’t generate a response body and a body is allowed.
Example
# hide all 404s at end of config by setting 403
static;
if resp.status == 404 { set_status 403; }
Request headers
All header values that get set are parsed as patterns.
req_header.add (action)
adds a new request header line
req_header.add (name, value);
- name
- header name
- value
- pattern header value
Same as header.add for request headers.
req_header.append (action)
appends value to request header line
req_header.append (name, value);
- name
- header name
- value
- pattern header value
Same as header.append for request headers.
req_header.overwrite (action)
overwrite request header line or add new one
req_header.overwrite (name, value);
- name
- header name
- value
- pattern header value
Same as header.overwrite for request headers.
req_header.remove (action)
remove existing request header
req_header.remove name;
- name
- header name
Same as header.remove for request headers.
Example
Remove Accept-Encoding
request header to workaround the BREACH vulnerability in https.
if request.scheme == "https" {
# create a copy of the header value
req_header.add "HTTPS-Accept-Encoding" => '%{req.header[Accept-Encoding]}';
req_header.remove "Accept-Encoding";
}
io.buffer_out (action)
set memory limit for outgoing chunkqueues (default is 256KiB)
io.buffer_out limit;
- limit
- limit in bytes (0 means unlimited)
Example
io.buffer_out 512kbyte;
io.buffer_in (action)
set memory limit for incoming chunkqueues (default is 256KiB)
io.buffer_in limit;
- limit
- limit in bytes (0 means unlimited)
Example
io.buffer_in 512kbyte;
map (action)
maps the result of a pattern to a user defined action
map (pattern, mapping);
- pattern
- the evaluation of this pattern is used as key in the mapping
- mapping
- maps strings (or default) to actions
The pattern is parsed as pattern. Have a look at mod_vhost for special mappings on hostnames.
Example
map "%{req.path}" => [
"/" => {
respond 200 => "root";
},
"/news" => {
respond 200 => "news";
},
default => {
respond 404;
},
];
listen (setup)
listen to a socket address, see above for accepted formats (default TCP port is 80)
listen socket-address;
- socket-address
- socket address to listen to
Example
setup {
listen "0.0.0.0";
listen "[::]";
listen "127.0.0.1:8080";
}
workers (setup)
sets worker count; each worker runs in its own thread and works on the connections it gets assigned from the master worker
workers count;
- count
- number of workers (default is 1)
Example
setup {
workers 2;
}
workers.cpu_affinity (setup)
binds worker threads to a cpu, only available on Linux systems
workers.cpu_affinity mapping;
- mapping
- list of integers or a list of lists of integers
Example
workers.cpu_affinity [0, 1];
module_load (setup)
load the given module(s)
module_load names;
- names
- string or list of strings with the module name(s)
modules can be “loaded” more than once without error
Example
setup {
module_load "mod_rewrite";
}
io.timeout (setup)
sets the global I/O timeout (wait for network read and write)
io.timeout timeout;
- timeout
- timeout value in seconds, default is 300s
stat_cache.ttl (setup)
set TTL for stat cache entries
stat_cache.ttl ttl;
- ttl
- time to live in seconds, default is 10s
tasklet_pool.threads (setup)
sets number of background threads for blocking tasks
tasklet_pool.threads threads;
- threads
- number of threads
For example the stat cache uses such background threads.
if threads = 0
the tasks are run in foreground (no background threads).
if threads < 0
all worker share a GThreadPool.
if threads > 0
each worker has its own thread pool with threads
threads.
fetch.files_static (setup)
starts a Fetch API provider
fetch.files_static (name, filename-pattern);
- name
- name of the storage
- filename-pattern
- A filename pattern including exactly on *
Loads all filenames matching the wildcard pattern (which must include exactly on *
) into the fetch storage.
Example
setup {
fetch.files_static "sni" => "/etc/certs/lighttpd_sni_*.pem";
}
mod_access
mod_access lets you filter clients by IP address.
access.deny (action)
denies access by returning a 403 status code
access.deny;
access.check (action)
allows or denies access based on client IP address
access.check rules;
- rules
- A key value list mapping "access" and/or "deny" keys to a list of CIDR addresses or "all".
Checks the client IP address against the rules. Default is to deny all addresses. The most precise matching rule defines the result (“192.168.100.0/24” takes precedence over “192.168.0.0/16”; similar to routing tables); if the same CIDR is in both lists the second action is taken. “all” is a synonym for “0.0.0.0/0” and “::/0”, matching all IPv4 and IPv6 addresses.
Example: restrict access to local network
Limit access to clients from the local network. The deny rule isn’t strictly required, as the default is to deny anyway. The smaller CIDR strings for the local networks override the global deny rule.
setup {
module_load "mod_access";
}
access.check (
"allow" => ("127.0.0.0/8", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"),
"deny" => ("all")
);
Example: restrict access to subnet with exception
Limit access to clients from “192.168.10.0/24”, but deny access to “192.168.10.1”. As “192.168.10.1” (equivalent to “192.168.10.1/32”) is a more precise match it overwrites the allow rule for the subnet “192.168.10.0/24” containing it.
setup {
module_load "mod_access";
}
access.check (
"allow" => ("192.168.10.0/24"),
"deny" => ("192.168.10.1")
);
access.redirect_url (option)
url to redirect to if access was denied (not implemented yet)
access.redirect_url url;
NOT IMPLEMENTED YET
access.log_blocked (option)
whether to log when access was denied (with log level "info")
access.log_blocked url;
mod_accesslog
mod_accesslog logs requests handled by lighttpd to files, pipes or syslog. The format of the logs can be customized using printf-style placeholders.
accesslog.format (option)
defines the log format
accesslog.format format;
Some format specifiers take a mandatory key, enclosed in curly braces between percent sign and the actual specifier. CLF means “common log format”, if a value is zero, a ‘-‘ is used instead.
specifier | description |
---|---|
%% | Percent sign itself |
%a | Remote IP-address |
%A | Local IP-address |
%b | Size of response in bytes, excluding HTTP headers (CLF) |
%B | Size of response in bytes, excluding HTTP headers |
%{foobar}C | (not implemented yet) Contents of cookie foobar of the request |
%D | Time taken to serve the request in microseconds |
%{foobar}e | Contents of the request environment variable foobar
|
%f | Path to physical file |
%h | Remote IP-address (same as %a ) |
%{foobar}i | Contents of request header foobar
|
%m | Request method (GET, POST, etc) |
%{foobar}o | Contents of response header foobar
|
%p | Local port |
%q | Querystring |
%r | First line of request (GET /foo.html?bar HTTP/1.1) |
%s | Response status code |
%t | Time/date the request was received in standard english format |
%T | Time taken to serve the request in seconds |
%u | Authed user (from mod_auth). Same as %{REMOTE_USER}e
|
%U | Request path (not including querystring) |
%v | Server name as set through the server.name option or the request hostname of server.name is not set |
%V | Request hostname |
%X | Connection status after response: “X” if aborted before completed, “+” if keepalive, “-“ if no keepalive |
%I | Bytes received including HTTP headers and request body |
%O | Bytes sent including HTTP headers and response body |
Modifiers right after the percent sign like Apache provides them, are not supported. “<” or “>” are ignored, everything else results in a parse error. Specifiers supported by Apache but not lighty: %l, %n, %P
Example
accesslog.format "%h %V %u %t \"%r\" %>s %b";
accesslog (option)
defines the log target
accesslog target;
Enable logging by setting a log target. Supports the same log targets as log.
Example
setup {
module_load "mod_accesslog";
accesslog "/var/log/lighttpd/access.log";
}
mod_auth
mod_auth requires authentication from clients using a username and password. It supports the basic (digest not yet) authentication method as well as plaintext, htpasswd and htdigest backends.
IMPORTANT NOTE: You need to put the auth actions before generating content! If a content handler is already active (like php or static or dirlist), auth will be ignored!
- Basic:
The “basic” method transfers the username and the password in cleartext over the network (base64 encoded) and might result in security problems if not used in conjunction with an encrypted communication channel between client and server.
It is recommend to use https in conjunction with basic authentication. - Digest (not supported yet):
The “digest” method only transfers a hashed value over the network which performs a lot of work to harden the authentication process in insecure networks (like the internet).
The “digest” method doesn’t work with the htpasswd backend, only with plaintext and htdigest.
NOTE: The digest method is broken in Internet Explorer < 7. Use basic instead if this is a problem for you. (not supported for now anyway)
auth.plain (action)
requires authentication using a plaintext file
auth.plain options;
- options
- A key-value table with the following entries:
- method
- "basic" or "digest" - for now only "basic" is supported, but you still have to specify it.
- realm
- the realm name to send in the "Need authentication" response to the browser; used in the hash for htdigest too.
- file
- the filename of the backend data
- ttl
- (optional) after how many seconds lighty reloads the password file if it got changed and is needed again (defaults to 10 seconds)
requires authentication using a plaintext file containing user:password pairs separated by newlines (\n).
auth.htpasswd (action)
requires authentication using a htpasswd file
auth.htpasswd options;
- options
- A key-value table with the following entries:
- method
- only "basic" is supported
- realm
- the realm name to send in the "Need authentication" response to the browser; used in the hash for htdigest too.
- file
- the filename of the backend data
- ttl
- (optional) after how many seconds lighty reloads the password file if it got changed and is needed again (defaults to 10 seconds)
- requires authentication using a htpasswd file containing user:encrypted_password pairs separated by newlines (\n)
- passwords are encrypted using crypt(3), use the htpasswd binary from apache to manage the file
- hashes starting with “$apr1$” ARE supported (htpasswd -m)
- hashes starting with “{SHA}” ARE supported (followed by sha1_base64(password), htpasswd -s)
auth.htdigest (action)
requires authentication using a htdigest file
auth.htdigest options;
- options
- A key-value table with the following entries:
- method
- "basic" or "digest" - for now only "basic" is supported, but you still have to specify it.
- realm
- the realm name to send in the "Need authentication" response to the browser; used in the hash for htdigest too.
- file
- the filename of the backend data
- ttl
- (optional) after how many seconds lighty reloads the password file if it got changed and is needed again (defaults to 10 seconds)
- requires authentication using a htdigest file containing user:realm:hashed_password tuples separated by newlines (\n)
- the hashes are bound to the realm, so you can’t change the realm without resetting the passwords
- passwords are saved as (modified) md5 hashes:
md5hex(username + ":" + realm + ":" + password)
auth.deny (action)
handles request with "401 Unauthorized"
auth.deny;
auth.debug (option)
enable debug output
auth.debug value;
Example
setup {
module_load "mod_auth";
}
#/members/ is for known users only
if request.path =^ "/members/" {
auth.plain [ "method" => "basic", "realm" => "members only", "file" => "/etc/lighttpd/users.txt"];
if req.env["REMOTE_USER"] !~ "^(admin1|user2|user3)$" { auth.deny; }
}
Example
You can use auth.require_user
from the mod_lua plugin contrib/core.lua for the REMOTE_USER check too:
setup {
module_load ("mod_auth", "mod_lua");
lua.plugin "contrib/core.lua";
}
#/members/ is for known users only
if request.path =^ "/members/" {
auth.plain [ "method" => "basic", "realm" => "members only", "file" => "/etc/lighttpd/users.txt"];
auth.require_user ("admin1", "user2", "user3");
}
mod_balance
mod_balance balances between different backends.
Using an action from mod_balance also activates a backlog: lighttpd2 will then put requests in a backlog if no backend is available and try again later.
Be careful: the referenced actions may get executed more than once (until one is successful!), so don’t loop rewrites in them or something similar.
balance.rr (action)
balance between actions (list or single action) with Round-Robin
balance.rr actions;
- actions
- the actions to balance between
Round-Robin (rr) the requests are distributed equally over all backends.
Example
balance.rr { fastcgi "127.0.0.1:9090"; };
Example
balance.rr ({ fastcgi "127.0.0.1:9090"; }, { fastcgi "127.0.0.1:9091"; });
balance.sqf (action)
balance between actions (list or single action) with SQF
balance.sqf actions;
- actions
- the actions to balance between
Shortest-Queue-First (sqf) is similar to Round-Robin and prefers the backend with the shortest wait-queue.
Example
balance.sqf { fastcgi "127.0.0.1:9090"; };
balance.debug (option)
enable debug output
balance.debug value;
mod_cache_disk_etag
mod_cache_disk_etag caches generated content on disk if an etag response header is set; if the backend sends an already cached etag, the backend is closed and the file is sent directly.
Please note: This will not skip the backend, as it will need at least the response headers.
Hint:
Use a cron-job like the following to remove old cached data, e.g. in crontab daily:
find /var/cache/lighttpd/cache_etag/ -type f -mtime +2 -exec rm -r {} \;
Hint:
Have a look at mod_deflate to see this module in action.
cache.disk.etag (action)
cache responses based on the ETag response header
cache.disk.etag path;
- path
- directory to store the cached results in
This blocks action progress until the response headers are done (i.e. there has to be a content generator before it (like fastcgi/dirlist/static file). You could insert it multiple times of course (e.g. before and after deflate).
Example
setup {
module_load "mod_cache_disk_etag";
}
cache.disk.etag "/var/lib/lighttpd/cache_etag"
mod_debug
mod_debug offers various utilities to aid you debug a problem.
debug.show_connections (action)
shows a page similar to the one from mod_status, listing all active connections
debug.show_connections;
By specifying one or more “connection ids” via querystring (parameter “con”), one can request additional debug output for specific connections.
Example
if req.path == "/debug/connections" { debug.show_connections; }
debug.profiler_dump (action)
dumps all allocated memory to the profiler output file if profiling enabled
debug.profiler_dump;
lighttpd2 needs to be compiled with profiler support, and profiling has to be enabled by setting the environment variable LIGHTY_PROFILE_MEM
to the path of a target log file.
debug.show_events (action)
shows a plain text list of all events
debug.show_events;
this is a very low level debug tool for developers.
Example
if req.path == "/debug/events" { debug.show_events; }
debug.show_events_after_shutdown (setup)
time in seconds after start of shutdown to log remaining active events
debug.show_events_after_shutdown (timeout, repeat);
- timeout
- timeout after which to display events (default: disabled)
- repeat
- timeout after which to repeat displaying events (default: disabled)
this is a very low level debug tool for developers; it shows which event listeners keep lighttpd2 alive when it should stop.
Example
setup { debug.show_events_after_shutdown 5; }
setup { debug.show_events_after_shutdown 5, 15; }
mod_deflate
mod_deflate mod_deflate compresses content on the fly
deflate (action)
waits for response headers; if the response can be compressed deflate adds a filter to compress it
deflate options;
- options
- A key-value table with the following entries:
- encodings
- supported method, depends on whats compiled in (default: "deflate,gzip,bzip2")
- blocksize
- blocksize is the number of kilobytes to compress at one time, it allows the webserver to do other work (network I/O) in between compression (default: 4096)
- output-buffer
- output-buffer is a per connection buffer for compressed output, it can help decrease the response size (fewer chunks to encode). If it is set to zero a shared buffer will be used. (default: 4096)
- compression-level
- 0-9: lower numbers means faster compression but results in larger files/output, high numbers might take longer on compression but results in smaller files/output (depending on files ability to be compressed), this option is used for all selected encoding variants (default: 1)
Example
deflate [ "encodings" => "deflate,gzip,bzip2", "blocksize" => 4096, "output-buffer" => 4096, "compression-level" => 1 ];
Example
deflate [ "compression-level" => 6 ];
deflate.debug (option)
enable debug output
deflate.debug value;
Notes
Important: As deflate;
waits for the response headers, you must handle the request before it (see below how to check whether the request is handled).
If the request is not handled, you will get a “500 - Internal error” and a message in the error.log.
Does not compress:
- response status: 100, 101, 204, 205, 206, 304
- already compressed content
- if more than one etag response header is sent
- if no common encoding is found
Supported encodings
- gzip, deflate (needs zlib)
- bzip2 (needs bzip2)
deflate
also:
- modifies etag response header (if present)
- adds “Vary: Accept-Encoding” response header
- resets Content-Length header
Simple config
setup {
module_load "mod_deflate";
}
# define our own "action"
do_deflate = {
# make sure static files get handled before we try deflate
static;
# we can only wait for response headers if we already have a request handler! (static only handles GET/HEAD requests)
if request.is_handled {
# limit content-types we want to compress -> see mimetypes
if response.header["Content-Type"] =~ "^(.*/javascript|text/.*)(;|$)" {
deflate;
}
}
};
# now add do_deflate; in places where you want deflate. for example at the end of your config:
do_deflate;
Extended config (makes use of mod_cache_disk_etag)
setup {
module_load ("mod_deflate", "mod_cache_disk_etag");
}
# define our own "action"
do_deflate = {
# make sure static files get handled before we try deflate
static;
# we can only wait for response headers if we already have a request handler! (static only handles GET/HEAD requests)
if request.is_handled {
# limit content-types we want to compress -> see mimetypes
if response.header["Content-Type"] =~ "^(.*/javascript|text/.*)(;|$)" {
deflate;
# the following block needs mod_cache_disk_etag (and is optional)
# only cache compressed result of static files
if physical.is_file {
cache.disk.etag "/var/cache/lighttpd/cache_etag";
}
}
}
};
# now add do_deflate; in places where you want deflate. for example at the end of your config:
do_deflate;
mod_dirlist
mod_dirlist lists files inside a directory. The output can be customized in various ways from style via css to excluding certain entries.
dirlist (action)
lists file in a directory
dirlist options;
- options
- A key-value table with the following entries:
- css
- string: url to external stylesheet (default: inline internal css)
- hide-dotfiles
- boolean: hide entries beginning with a dot (default: true)
- hide-tildefiles
- boolean: hide entries ending with a tilde (~), often used for backups (default: true)
- hide-directories
- boolean: hide directories from the directory listing (default: false)
- include-header
- boolean: include HEADER.txt above the directory listing (default: false)
- hide-header
- boolean: hide HEADER.txt from the directory listing (default: false)
- encode-header
- boolean: html-encode HEADER.txt (if included), set to false if it contains real HTML (default: true)
- include-readme
- boolean: include README.txt below the directory listing (default: true)
- hide-readme
- boolean: hide README.txt from the directory listing (default: false)
- encode-readme
- boolean: html-encode README.txt (if included), set to false if it contains real HTML (default: true)
- exclude-suffix
- list of strings: hide entries that end with one of the strings supplied (default: empty list)
- exclude-prefix
- list of strings: hide entries that begin with one of the strings supplied (default: empty list)
- debug
- boolean: output debug information to log (default: false)
- content-type
- string: content-type to return in HTTP response headers (default: "text/html; charset=utf-8")
Example
shows a directory listing including the content of HEADER.txt above the list and hiding itself from it; also hides all files ending in “.bak”
setup {
module_load ("mod_dirlist");
}
if req.path =^ "/files/" {
dirlist ("include-header" => true, "hide-header" => true, "hide->suffix" => (".bak"));
}
mod_expire
mod_expire add "Expires" and "Cache-Control" headers to the response
This allows you to control client-side caching of responses based on a simple rule/formula.
If a response is cached using an “Expires” and “Cache-Control” header, then the client will not issue a new request for it until the date specified by the header is reached.
Adding expire headers to static content like css, javascript, images or similar, can greatly reduce the amount of requests you get and therefor save resources.
Use “modification” as <base>
if your content changes in specific intervals like every 15 minutes.
expire (action)
adds an "Expires" header to the response
expire rule;
- rule
- the rule to calculate the "Expires" header value with
The rule/formula used here, complies with the one mod_expire for Apache uses:
<base> [plus] (<num> <type>)+
-
<base>
is one of “access”, “now” or “modification”; “now” being equivalent to “access”. - “
plus
” is optional and does nothing. -
<num>
is any positive integer. -
<type>
is one of “seconds”, “minutes”, “hours”, “days”, “weeks”, “months” or “years”.
The trailing “s” in <type>
is optional.
If you use “modification” as <base>
and the file does not exist or cannot be accessed, mod_expire will do nothing and request processing will go on.
The expire action will overwrite any existing “Expires” header.
It will append the max-age value to any existing “Cache-Control” header.
Example
Cache image, css, txt and js files for 1 week.
setup {
module_load "mod_expire";
}
if req.path =~ "\.(jpe?g|png|gif|txt|css|js)$" {
expire "access plus 1 week";
}
mod_fastcgi
mod_fastcgi connect to FastCGI backends for generating response content
fastcgi (action)
connect to FastCGI backend
fastcgi socket;
- socket
- socket to connect to, either "ip:port" or "unix:/path"
Don’t confuse FastCGI with CGI! Not all CGI backends can be used as FastCGI backends (but you can use fcgi-cgi to run CGI backends with lighttpd2).
Example
fastcgi "127.0.0.1:9090"
Example
Start php for example with spawn-fcgi: spawn-fcgi -n -s /var/run/lighttpd2/php.sock -- /usr/bin/php5-cgi
setup {
module_load "mod_fastcgi";
}
if phys.path =$ ".php" and phys.is_file {
fastcgi "unix:/var/run/lighttpd2/php.sock";
}
fastcgi.log_plain_errors (option)
whether to prepend timestamp and other info to FastCGI stderr lines in the "backend" log
fastcgi.log_plain_errors value;
mod_flv
mod_flv provides flash pseudo streaming
flv (action)
pseudo stream the current file as flash
flv;
Lets flash players seek with the “start” query string parameter to an (byte) offset in the file, and prepends a simple flash header before streaming the file from that offset.
Uses “video/x-flv” as hard-coded content type.
Example
if phys.path =$ ".flv" {
flv;
}
Example
Use caching and bandwidth throttling to save traffic. Use a small burst threshold to prevent the player from buffering at the beginning.
This config will make browsers cache videos for 1 month and limit bandwidth to 150 kilobyte/s after 500 kilobytes.
if phys.path =$ ".flv" {
expire "access 1 month";
io.throttle 500kbyte => 150kbyte;
flv;
}
mod_fortune
mod_fortune loads quotes (aka fortune cookies) from a file and provides actions to add a random quote as response header (X-fortune) or display it as a page.
fortune.load (setup)
loads cookies from a file, can be called multiple times to load data from multiple files
fortune.load filename;
- filename
- the file to load the cookies from
fortune.header (action)
adds a random quote as response header "X-fortune".
fortune.header;
fortune.page (action)
shows a random cookie as response text
fortune.page;
Example
setup {
module_load "mod_fortune";
fortune.load "/var/www/fortunes.txt";
}
if req.path == "/fortune" {
fortune.page;
} else {
fortune.header;
}
mod_gnutls
mod_gnutls listens on separate sockets for TLS connections (https) using GnuTLS
gnutls (setup)
setup a TLS socket
gnutls options;
- options
- A key-value table with the following entries:
- listen
- (mandatory) the socket address to listen on (same as "listen":plugin_core.html#plugin_core__setup_listen), can be specified more than once to setup multiple sockets with the same options
- pemfile
- (mandatory) file containing the private key, certificate, intermediate certificates (the root certificate is usually not included) and an OCSP response; alternatively it can be a key-value list with a "key" and a "cert" entry, and optionally a "ocsp" entry.
- pin
- the PIN (or password) to use when using PKCS #11 modules or encrypted keys. The pin is kept in memory.
- priority
- GnuTLS priority string, specifying ciphers and other GnuTLS options (default: "NORMAL")
- dh-params
- filename with generated dh-params (default: fixed 4096-bit parameters)
- protect-against-beast
- whether to force RC4 on TLS1.0 and SSL3.0 connections by appending ":-CIPHER-ALL:+ARCFOUR-128" to the priority string (default: false)
- session-db-size
- size of session database, 0 to disable session support (TLS ticket support is always enabled if GnuTLS supports it)
- sni-backend
- "fetch" backend name to search certificates in with the SNI servername as key (only available if SNI in lighttpd2 was enabled)
- sni-fallback-pemfile
- certificate to use if request contained SNI servername, but the sni-backend didn't find anything; if request didn't contain SNI the standard "pemfile"(s) are used; similarly with "pemfile" it can also be a key-value list with a "key" and a "cert" entry.
Simple TLS on IPv4 and IPv6
setup {
module_load "mod_gnutls";
gnutls (
"priority" => "PFS:-3DES-CBC:-ARCFOUR-128:-VERS-SSL3.0:-SHA1:+SHA1:+RSA:%SERVER_PRECEDENCE",
"listen" => "0.0.0.0:443",
"listen" => "[::]:443",
"pemfile" => "/etc/certs/lighttpd.pem"
);
}
TLS with simple SNI
setup {
module_load "mod_gnutls";
gnutls (
"priority" => "PFS:-3DES-CBC:-ARCFOUR-128:-VERS-SSL3.0:-SHA1:+SHA1:+RSA:%SERVER_PRECEDENCE",
"listen" => "0.0.0.0:443",
"listen" => "[::]:443",
"pemfile" => "/etc/certs/www.example.com.pem"
"pemfile" => "/etc/certs/mail.example.com.pem"
);
}
TLS with SNI from fetch backend
For a SNI hostname example.com
lighttpd2 will try to find the private key and certificate(s) in /etc/certs/lighttpd_sni_example.com.pem
.
setup {
fetch.files_static "sni" => "/etc/certs/lighttpd_sni_*.pem";
module_load "mod_gnutls";
gnutls (
"priority" => "PFS:-3DES-CBC:-ARCFOUR-128:-VERS-SSL3.0:-SHA1:+SHA1:+RSA:%SERVER_PRECEDENCE",
"sni-backend" => "sni",
"listen" => "0.0.0.0:443",
"listen" => "[::]:443",
"pemfile" => "/etc/certs/lighttpd.pem"
);
}
Simple TLS on IPv4 and IPv6 with separate files for key and certificate and encrypted key
setup {
module_load "mod_gnutls";
gnutls (
"priority" => "PFS:-3DES-CBC:-ARCFOUR-128:-VERS-SSL3.0:-SHA1:+SHA1:+RSA:%SERVER_PRECEDENCE",
"listen" => "0.0.0.0:443",
"listen" => "[::]:443",
"pin" => "passwordForEncryptedKey",
"pemfile" => (
"key" => "/etc/certs/lighttpd_key.pem",
"cert" => "/etc/certs/lighttpd_cert.pem"
)
);
}
Simple TLS on IPv4 and IPv6 with SoftHSM
setup {
module_load "mod_gnutls";
gnutls (
"priority" => "PFS:-3DES-CBC:-ARCFOUR-128:-VERS-SSL3.0:-SHA1:+SHA1:+RSA:%SERVER_PRECEDENCE",
"listen" => "0.0.0.0:443",
"listen" => "[::]:443",
"pin" => "SoftHSM-pin",
"pemfile" => (
"key" => "pkcs11:model=SoftHSM;manufacturer=SoftHSM;serial=1;token=master-key;id=%ac%d5%52%69%16%09%2c%0c%9c%b0%ec%6c%3d%3b%c6%4d%55%4c%40%49;object=my-key;object-type=private",
"cert" => "pkcs11:model=SoftHSM;manufacturer=SoftHSM;serial=1;token=master-key;id=%ac%d5%52%69%16%09%2c%0c%9c%b0%ec%6c%3d%3b%c6%4d%55%4c%40%49;object=my-key;object-type=cert"
)
);
}
Simple TLS on IPv4 and IPv6 with OCSP stapling
setup {
module_load "mod_gnutls";
gnutls (
"priority" => "PFS:-3DES-CBC:-ARCFOUR-128:-VERS-SSL3.0:-SHA1:+SHA1:+RSA:%SERVER_PRECEDENCE",
"listen" => "0.0.0.0:443",
"listen" => "[::]:443",
"pemfile" => (
"key" => "/etc/certs/lighttpd.pem",
"cert" => "/etc/certs/lighttpd.pem",
"ocsp" => "/etc/certs/lighttpd-ocsp.der",
)
);
}
Server Name Indication (SNI)
TLS SNI means that a client can send the hostname of the server it tries to connect to unencrypted in the TLS handshake.
If you want to host multiple hostnames on the same IP address (quite common) there are some options how to do it (they can be combined):
- Use a wildcard as “CommonName” (CN) in the certificate like
*.example.com
(although this usually doesn’t matchexample.com
) - Use “Subject Alternative Names” in the certificate
- Provide different certificates based on the SNI hostname in the TLS handshake.
Clients supporting SNI usually support the other options too, but not all clients support SNI.
GnuTLS has some basic SNI support built in; if you specify multiple pemfile
s in the options, it will pick the first with a certificate that matches the SNI hostname.
lighttpd2 has an optional extended SNI support (which has to be enabled at compile time, and is required for the sni-*
options to be available). It is designed to load certificates asynchronously from backends (files, SQL, …) on demand, using the fetch API.
In this case lighttpd2 will fetch the certificate based on the SNI hostname from the given sni-backend
before GnuTLS is started for a connection.
If a SNI hostname was given by the client, but no certificate was found in the backend, it will use sni-fallback-pemfile
(if set) instead of pemfile
.
Combining sni-backend
, sni-fallback-pemfile
and multiple pemfile
s won’t work - it will only use the first configured pemfile
(if no SNI hostname was given by the client, otherwise sni-*
certificates are used).
Also note that lighttpd2 does NOT verify whether the SNI hostname matches the hostname in the HTTP request. SNI is only used by the client to tell the server for which hostname it should send the certificate (i.e. what the client is using to verify it).
GnuTLS priority string
The GnuTLS priority string configures which ciphers and protocol versions are available, and also a small set of options (workarounds to activate and so on).
Example: "SECURE:-VERS-SSL3.0:-SHA1:+SHA1:-RSA:+RSA:%SERVER_PRECEDENCE"
-
SECURE
: starts withSECURE
level: only secure ciphers and secure curves -
-VERS-SSL3.0
: disables SSL3.0 support (all clients should support at least TLS1.0 now) -
-SHA1:SHA1
puts SHA1 back at the list for MAC algorithms (preferring the other SHA variants) -
-RSA:+RSA
: prefer (RSA) DHE/ECDHE key exchanges over simple RSA -
%SERVER_PRECEDENCE
a flag telling GnuTLS to use its own order of preference instead of the order provided by the client.
See also:
- Priority strings (GnuTLS manual)
- GnuTLS Priority Strings
gnutls-cli -l --priority "SECURE:-VERS-SSL3.0:-SHA1:+SHA1:-RSA:+RSA:%SERVER_PRECEDENCE"
OCSP stapling
OCSP stapling is used to assure a client the certificate is still valid (i.e. not revoked); you can put an OCSP response into the certificate file in an “OCSP RESPONSE”-PEM block (there is probably no standard for this, just base64-encode the DER-response you have), or specify the (DER or PEM formatted) OCSP response as separate file using “ocsp” in a “pemfile” block.
Server Name Indication (SNI) should work fine, as an OCSP response is only used if it matches the certificate in use for the connection.
The fetch backends do support OCSP stapling if the OCSP response is appended as PEM block.
Lighttpd does NOT automatically reload OCSP responses; you have to restart to load new OCSP responses (a cron job is probably the right way to do it).
If you have your certificate and the issuer-certificate (the one that signed yours) in separate files you can request an OCSP response like that (using the GnuTLS “ocsptool”):
ocsptool --ask --load-issuer issuer.pem --load-cert cert.pem --outfile ocsp.der
Converting into PEM format can be done like this:
(echo "-----BEGIN OCSP RESPONSE-----"; base64 --wrap=64 ocsp.der; echo "-----END OCSP RESPONSE-----") > ocsp.pem
If you have trouble identifying which certificates you need, here the more detailed explanation:
You usually have a list of certificates in the PEM file you pass to lighttpd. The first certificate usually has a “Subject” pointing to your server name (CN), like: “Subject: CN=lighttpd.net”. It also has a “Issuer” attribute (like “C=US, O=Let’s Encrypt, CN=Let’s Encrypt Authority X3”). The issuer certificate needs a “Subject” matching that “Issuer”, and should be the second certificate in the PEM file (unless it already is the root CA, in which case it is usually omitted).
ocsptool
will always use the first certificate in a file and ignore the others, so you can use the normal PEM file you pass to lighttpd as argument after --load-cert
, but you need to extract the issuer certificate if you don’t have it in a separate file. The following awk
script extracts the second PEM block from a file:
awk '
BEGIN { block = 0 }
/^-----BEGIN / { ++block; }
{ if (block > 1) print; }
' "certs.pem" > "issuer.pem"
protect-against-beast
BEAST is considered mitigated on clients by many now, so this workaround is no longer recommended and disabled by default.
It enabled it will enforce the usage of RC4 on TLS1.0 and before (TLS1.1 and TLS1.2 are not vulnerable); but RC4 has issues on its own.
DH parameters
The DHE_RSA
key exchange requires parameters (similar to the curves used for ECDHE_RSA
); the parameters specify a prime p
and a group generator g
of the multiplicative group of integers modulo p
(i.e. for all x
in 1..p-1
exists an e
with g^e = x
); sometimes g
might only create a sufficiently large subgroup (for example of size (p-1)/2
).
The security of the DH key exchange depends (among other things) on the bit length of p
; therefore lighttpd includes default 4096-bit parameters (provided by the GnuTLS certtool with certtool --get-dh-params --bits=4096
in version 3.0.22), and these should be safe to use (key lengths > 1024 are not supported by some older clients; if you need to support those you either have to disable DH key exchange or specify 1024-bit parameters).
You can use either GnuTLS or openssl to generate your own parameters:
certtool --generate-dh-params --bits=2048
openssl dhparam -dsaparam 2048
openssl dhparam -2 2048
openssl dhparam -5 2048
The GnuTLS certtool
only generates “DSA” parameters (any prime with a large generator), while openssl
can generate “DH” parameters with a generator of 2 or 5 combined with a Sophie Germain prime (p-1)/2
(i.e. both p
and (p-1)/2
are primes) or “DSA” parameters using the -dsaparam
option.
“DH” parameters take a lot more time to generate, but you can reuse keys for those (although it is not recommended, search for SSL_OP_SINGLE_DH_USE
in the openssl manual on SSL_CTX_set_tmp_dh_callback
). Keys for “DSA” parameters should not be reused, i.e. one should generate a new private key for each connection, which is the case in mod_gnutls. The default parameters provided by lighttpd are “DH” parameters.
See also:
mod_limit
mod_limit limits concurrent connections or requests per second.
Both limits can be “in total” or per IP.
limit.con (action)
limits the total amount of concurrent connections to the specified limit.
limit.con (limit, action);
- limit
- the maximum number of concurrent connections
- action
- (optional) an action to be executed when the limit is reached
If no action is defined a 503 error page will be returned. If it is specified there is no other special handling apart from running the specified action when the limit is reached.
Example
limit.con 10;
limit.con_ip (action)
limits the total amount of concurrent connections per IP to the specified limit.
limit.con_ip (limit, action);
- limit
- the maximum number of concurrent connections per IP
- action
- (optional) an action to be executed when the limit is reached
If no action is defined a 503 error page will be returned. If it is specified there is no other special handling apart from running the specified action when the limit is reached.
Example
limit.con_ip 2;
limit.req (action)
limits the amount of requests per second to the specified limit.
limit.req (limit, action);
- limit
- the maximum number of requests per second
- action
- (optional) an action to be executed when the limit is reached
If no action is defined a 503 error page will be returned. If it is specified there is no other special handling apart from running the specified action when the limit is reached.
Example
limit.req 100;
limit.req_ip (action)
limits the amount of requests per second per IP to the specified limit.
limit.req_ip (limit, action);
- limit
- the maximum number of requests per second per IP
- action
- (optional) an action to be executed when the limit is reached
If no action is defined a 503 error page will be returned. If it is specified there is no other special handling apart from running the specified action when the limit is reached.
Example
limit.req_ip 100;
Limiting concurrent connections
This config snippet will allow only 10 active downloads overall and 1 per IP. If the limit is exceeded, either because more than 10 people try to access this resource or one person tries a second time while having one download running already, they will be redirected to /connection_limit_reached.html.
setup {
module_load ("mod_limit","mod_redirect");
}
limit_reached = {
redirect "/connection_limit_reached.html";
};
if req.path =^ "/downloads/" {
limit.con 10 => limit_reached;
limit.con_ip 1 => limit_reached;
}
Limiting requests per second
This config snippet will write a message to the log containing the client IP address if the /login page is hit more than once in a second. It will however also not do anything else. The client will be able to use the /login page as often as he wants.
setup {
module_load "mod_limit";
}
if req.path == "/login" {
limit.req_ip 1 => { log.write "Possible bruteforce from %{req.remoteip}"; };
}
mod_lua
mod_lua load lua plugins and actions
Also see Lua API.
lua.plugin (setup)
load file as lua plugin
lua.plugin (filename, options, lua-args);
- filename
- the file containing the lua code
- options
- A key-value table with options; no options available yet
- lua-args
- arguments forwarded to the lua plugin
A lua plugin can register setup and action callbacks (like any C module) by creating a setups / actions table in the global lua namespace.
The filename and the third argument lua-args
are available as parameters in ...
in the lua script.
Example
setup {
module_load "mod_lua";
lua.plugin "secdownload.lua";
}
Example plugin
(see contrib/core.lua for a real example)
local filename, args = ...
-- args are from the lua.plugin line
local function simple(actionarg)
-- actionarg is the parameter from the 'single "/xxx";' action line
-- create an action:
return action.respond()
end
actions = {
["simple"] = simple,
}
lua.handler (action)
load file as lua config
lua.handler (filename, options, lua-args);
- filename
- the file containing the lua code
- options
- A key-value table with the following entries:
- ttl
- time in seconds after which the file is checked for modifications and reloaded. 0 disables reloading (default 0)
- lua-args
- arguments forwarded to the lua plugin
lua.handler is basically the same as include_lua with the following differences:
- each worker loads the lua file itself
- it isn’t loaded before it is used, so you won’t see errors in the script at load time
- it cannot call setup functions
- it supports arguments to the script (
local filename, args = ...
) - doesn’t lock the global lua lock, so it performs better when you use multiple workers
See contrib/core.lua for how we load some external actions like contrib/core__xsendfile.lua.
Example
setup {
module_load "mod_lua";
lua.plugin "secdownload.lua";
}
if req.path =^ "/app" {
lua.handler "/etc/lighttpd/pathrewrite.lua", [ "ttl" => 300 ], "/app";
}
mod_core (lua)
mod_core.lua provides some useful helpers written in lua
Install
By default distributions (and make install
) should provide the necessary files; but you can always find them in the contrib folder:
core.lua
core__cached_html.lua
core__xsendfile.lua
That way you can modify them for your own needs if you have to (although it is recommended to change the names of the files and the actions, so you don’t get conflicts).
lighttpd should search for core.lua
in the correct (install) locations, so you don’t need the absolute path here.
core.wsgi (action)
Splits the url into SCRIPT_NAME (the subdirectory a web application is mounted at) and PATH_INFO (the path the web application should route)
core.wsgi (prefix, action);
- prefix
- URL prefix ("subdirectory") the web application is mounted at
- action
- action block connecting to the wsgi backend
See Howto WSGI for an example.
WSGI applications expect the url to be split into SCRIPT_NAME
and PATH_INFO
(CGI environment variables); SCRIPT_NAME
is their “application root”, and PATH_INFO
the requested resource in the application.
By default, lighttpd uses an empty PATH_INFO
(unless you used the “pathinfo;” action, but this doesn’t help as we’re not dealing with static files here).
Important: WSGI is an “extension” of CGI; it doesn’t specify a transport protocol, you can use it with plain CGI, FastCGI or SCGI (or anything else that supports the basic CGI protocol)
Example
Example: Trac in /trac
, listening via FastCGI on unix:/var/run/lighttpd/trac.socket
.
setup {
module_load ("mod_lua", "mod_fastcgi");
lua.plugin "core.lua";
}
core.wsgi ( "/trac", { fastcgi "unix:/var/run/lighttpd/trac.socket"; } );
core.cached_html (action)
try to find a file for the current url with ".html" suffix, if we couldn't find a static file for the url yet and the url doesn't already have the ".html" suffix.
core.cached_html;
Example
setup {
module_load "mod_lua";
lua.plugin "core.lua";
}
docroot "/some/dynamic/app/public";
core.cached_html;
if physical.is_file {
header.add ("X-cleanurl", "hit");
} else {
header.add ("X-cleanurl", "miss");
fastcgi "/var/run/lighttpd/dynamic-app.sock";
}
core.xsendfile (action)
provides a simple X-Sendfile feature; send a "X-Sendfile: /path/to/file" response header from your backend
core.xsendfile docroot;
- docroot
- (optional) doc-root the files has be in
Example
setup {
module_load ("mod_lua", "mod_fastcgi");
lua.plugin "core.lua";
}
fastcgi "/var/run/lighttpd/dynamic-app.sock";
core.xsendfile "/some/dynamic/app/";
auth.require_user (action)
require a specific authenticated user
auth.require_user userlist;
- userlist
- list of usernames to allow
This helper constructs a regular expression to match against request.environment[“REMOTE_USER”], so the example below is the same as:
auth.plain [ "method" => "basic", "realm" => "test", "file" => "/etc/lighttpd2/test.plain" ];
if req.env["REMOTE_USER"] !~ "^(foo1|foo2)$" { auth.deny; }
This action uses lua only to create the action, no lua is executed to handle requests in this case.
Be careful: the empty username matches unauthenticated users.
Example
setup {
module_load "mod_lua";
lua.plugin "core.lua";
}
auth.plain [ "method" => "basic", "realm" => "test", "file" => "/etc/lighttpd2/test.plain" ];
auth.require_user ("foo1", "foo2");
mod_secdownload (lua)
mod_secdownload.lua protects files with a time limited code
Install
By default distributions (and make install
) should provide the necessary files; but you can always find them in the contrib folder:
secdownload.lua
secdownload__secdownload.lua
That way you can modify them for your own needs if you have to (although it is recommended to change the names of the files and the actions, so you don’t get conflicts).
secdownload (action)
protect files with a time limited code
secdownload options;
- options
- A key-value table with the following entries:
- prefix
- URL path prefix to protect; default "/"
- document-root
- where the secret files are stored on disk
- secret
- shared secret used to create and verify urls.
- timeout
- how long a generated url is valid in seconds (maximum allowed time difference); default is 60
The prefix
is not used to build the filename; include it manually in the document-root
(works like alias "/prefix" => "/docroot"
, see alias
).
secdownload doesn’t actually handle the (valid) request, it just provides the mapping to a filename (and rejects invalid requests).
Example
setup {
module_load "mod_lua";
lua.plugin "secdownload.lua";
}
secdownload [ "prefix" => "/sec/", "document-root" => "/secret/path", "secret" => "abc", "timeout" => 600 ];
Generating URLs
To generate URLs that are valid for secdownload
you need the same secret.
The url takes the form prefix + md5hex(secret + filepath + timestamp) + '/' + timestamp + filepath
; timestamp is the Unix time formatted as hexadecimal number.
For example with PHP:
$secret = "abc";
$uri_prefix = "/sec/";
# filename; please note file name starts with "/"
$f = "/secret-file.txt";
# current timestamp
$t = time();
$t_hex = sprintf("%08x", $t);
$m = md5($secret.$f.$t_hex);
# generate link
printf('<a href="%s%s/%s%s">%s</a>', $uri_prefix, $m, $t_hex, $f, $f);
The config example above would map this url to the file /secret/path/secret-file.txt
.
For more examples see mod_secdownload (lighttpd 1.4.x).
mod_memcached
mod_memcached caches content on memcached servers
lookup
tries to find data associated with the key, and returns it as http body with status 200 if it finds something.
store
stores a http body (generated by another backend) in memcached.
Caching always requires you to think about what you want; you cannot cache content that changes with every request!
So most of the time you probably want to set a TTL for the stored content in memcached; your users probably don’t need new content to be available the next second, perhaps 60 seconds is still good (obviously not true for a chat…).
The other way is to purge the keys in your dynamic backend; you can set the memcached content from your backend too, which probably is faster than memcached.store
.
If the key is longer than 255 bytes or contains characters outside the range 0x21 - 0x7e we will use a hash of it instead (for now sha1, but that may change).
memcached.lookup (action)
searches the content in a memcached database
memcached.lookup (options, action-hit, action-miss);
- options
- A key-value table with the following entries:
- server
- socket address as string (default: 127.0.0.1:11211)
- headers
- (boolean, not supported yet) whether to lookup headers too. if false content-type determined by request.uri.path (default: false)
- key
- pattern for lookup key (default: "%{req.path}")
- action-hit
- action to run on cache hit (lookup was successful)
- action-miss
- action to run on cache miss (lookup was not successful)
memcached.store (action)
stores the generated response in a memcached database
memcached.store options;
- options
- A key-value table with the following entries:
- server
- socket address as string (default: 127.0.0.1:11211)
- flags
- (integer) flags for storing data (default 0)
- ttl
- ttl for storing (default 30; use 0 if you want to cache "forever")
- maxsize
- maximum size in bytes we want to store (default: 64*1024)
- headers
- (boolean, not supported yet) whether to store headers too (default: false)
- key
- pattern for store key (default: "%{req.path}")
Example
setup {
module_load "mod_memcached";
}
memcached.lookup (["key" => "%{req.scheme}://%{req.host}%{req.path}"], {
header.add "X-Memcached" => "Hit";
}, {
header.add "X-Memcached" => "Miss";
docroot "/var/www";
# important: You need a content handler before memcached.store
static;
memcached.store ["key" => "%{req.scheme}://%{req.host}%{req.path}"];
});
Lua API
mod_memcached exports a Lua API to per-worker luaState
s too (for use in lua.handler):
memcached.new(address)
creates a new connection; a connection provides:
req = con:get(key, cb | vr)
req = con:set(key, value, cb | vr, [ttl])
con:setq(key, value, [ttl])
If a callback was given, the callback gets called with a response object; otherwise the response will be in req.response when ready.
The response object has:
-
code
: 1 – Ok, 2 – Not stored, 3 – Exists, 4 – Not found, 5 – Error -
error
: error message -
key
: the lookup key flags
ttl
cas
data
mod_openssl
mod_openssl listens on separate sockets for TLS connections (https) using OpenSSL
openssl (setup)
setup a TLS socket
openssl options;
- options
- A key-value table with the following entries:
- listen
- (mandatory) the socket address to listen on (same as "listen":plugin_core.html#plugin_core__setup_listen), can be specified more than once to setup multiple sockets with the same options
- pemfile
- (mandatory) file containing the private key, certificate and (optionally) intermediate certificates (the root certificate is usually not included)
- ca-file
- file containing the intermediate certificates
- ciphers
- OpenSSL ciphers string (default: "HIGH !aNULL !3DES +kEDH +kRSA !kSRP !kPSK")
- dh-params
- filename with generated dh-params (default: fixed 4096-bit parameters)
- ecdh-curve
- OpenSSL ecdh-curve name
- options
- list of OpenSSL options (default: NO_SSLv2, NO_SSLv3, CIPHER_SERVER_PREFERENCE, NO_COMPRESSION, SINGLE_DH_USE, SINGLE_ECDH_USE)
- verify
- enable client certificate verification (default: false)
- verify-any
- allow all CAs and self-signed certificates, for manual checking (default: false)
- verify-depth
- sets client verification depth (default: 1)
- verify-require
- abort clients failing verification (default: false)
- client-ca-file
- file containing client CA certificates (to verify client certificates)
For ciphers
see OpenSSL ciphers string
For options
see options. Explicitly specify the reverse flag by toggling the “NO_” prefix to override defaults.
Simple TLS on IPv4 and IPv6
setup {
module_load "mod_openssl";
openssl [
"listen" => "0.0.0.0:443",
"listen" => "[::]:443",
"pemfile" => "/etc/certs/lighttpd.pem",
"options" => ["ALL", "NO_TICKET"],
];
}
TLS with client certificate verification
setup {
module_load "mod_openssl";
openssl (
"listen" => "0.0.0.0:443",
"listen" => "[::]:443",
"pemfile" => "/etc/certs/lighttpd.pem",
"client-ca-file" => "/etc/certs/myCA.pem",
"verify" => true,
"verify-require" => true
);
}
TLS with any client certificate
setup {
module_load "mod_openssl";
openssl (
"listen" => "0.0.0.0:443",
"listen" => "[::]:443",
"pemfile" => "/etc/certs/lighttpd.pem",
"verify" => true,
"verify-any" => true,
"verify-depth" => 9
);
}
openssl.setenv "client-cert";
openssl.setenv (action)
set SSL environment strings
openssl.setenv list;
- list
- list of subsets to export
Supported subsets:
- “client” – set
SSL_CLIENT_S_DN_
short-named entries - “client-cert” – set
SSL_CLIENT_CERT
to client certificate PEM - “server” – set
SSL_SERVER_S_DN_
short-named entries - “server-cert” – set
SSL_SERVER_CERT
to server certificate PEM
mod_progress
mod_progress track connection progress (state) via a unique identifier
mod_progress lets you track connection progress (or rather state) using a lookup table in which connections are registered via a random unique identifier specified with the request.
It is most commonly used to implement progress bars for file uploads.
A request to the webserver is registered using the progress.track
action and being tracked by a random unique identifier supplied with the X-Progress-Id
querystring parameter.
From that moment on, other requests can fetch the state of the first request through the progress.show
action specifying the X-Progress-Id
used earlier.
Even after a tracked request finished and the connection to the client is gone, requests can for a limited amount of time get the status of it to see it as “done”.
Check the source code there for further insight.
progress.ttl (setup)
Time to live in seconds for entries after a disconnect in the internal lookup table
progress.ttl ttl;
- ttl
- time to live in seconds (default: 30)
progress.methods (option)
Request methods to track
progress.methods methods;
Example
setup {
module_load "mod_progress";
progress.methods ("PUT", "POST");
}
progress.track (action)
tracks the current request if the X-Progress-ID querystring key is supplied
progress.track;
progress.show (action)
returns state information about the request tracked with the ID specified by the X-Progress-ID querystring parameter
progress.show format;
- format
- (optional) output format, one of "legacy", "json" or "jsonp". Defaults to "json".
Output formats:
- legacy:
new Object({"state": "running"", "received": 123456, "sent": 0, "request_size": 200000, "response_size": 0})
- json:
{"state": "running", "received": 123456, "sent": 0, "request_size": 200000, "response_size": 0}
- jsonp:
progress({"state": "running", "received": 123456, "sent": 0, "request_size": 200000, "response_size": 0})
The function name (default “progress”) can be altered by supplying a X-Progress-Callback querystring parameter.
The JSON object can contain the following members:
-
state: One of
"unknown"
,"running"
,"done"
or"error"
. - received: Bytes received by lighty or uploaded by the client.
- request_size: Total size of request or uploaded file as specified via the Content-Length request header.
- sent: Bytes sent by lighty or downloaded by the client.
- response_size: Total size of response. Attention: this might grow over time in case of streaming from a backend.
- status: HTTP status code of response.
received
, request_size
, sent
and response_size
are only available if state
is "running"
or "done"
.
status
is only available if state
is "error"
.
Example
setup {
module_load "mod_progress";
}
if req.path == "/upload.php" { progress.track; }
if req.path == "/progress" { progress.show; }
progress.debug (option)
enable debug output
progress.debug value;
mod_proxy
mod_proxy connect to HTTP backends for generating response content
proxy (action)
connect to HTTP backend
proxy socket;
- socket
- socket to connect to, either "ip:port" or "unix:/path"
proxy uses request.raw_path
for the URL (including the query string) to send to the backend.
Example
setup {
module_load "mod_proxy";
}
proxy "127.0.0.1:8080";
mod_redirect
mod_redirect redirect clients by sending a http status code 301 plus Location header
It supports matching regular expressions and substitution with captured substrings as well as other placeholders.
redirect (action)
redirect clients
redirect rule;
- rule
- a simple target string or one rule, mapping a regular expression to a target string, or a list of rules.
- The target string is a pattern.
- The regular expressions are used to match the request path (always starts with “/”!)
- If a list of rules is given, redirect stops on the first match.
- By default the target string is interpreted as absolute uri; if it starts with ‘?’ it will replace the query string, if it starts with ‘/’ it will replace the current path, and if it starts with ‘./’ it is interpreted relative to the current “directory” in the path.
Example: redirect always (http to https)
setup {
module_load "mod_redirect";
}
if request.scheme == "http" {
if request.query == "" {
redirect "https://%{request.host}%{enc:request.path}";
} else {
redirect "https://%{request.host}%{enc:request.path}?%{request.query}";
}
}
Example: redirect always (prepend www)
setup {
module_load "mod_redirect";
}
if request.host !~ "^www\.(.*)$" {
if request.query == "" {
redirect "http://www.%{request.host}%{enc:request.path}";
} else {
redirect "http://www.%{request.host}%{enc:request.path}?%{request.query}";
}
}
Example: redirect if match
setup {
module_load "mod_redirect";
}
redirect "^/old_url$" => "http://new.example.tld/url"
Example
redirect all non www. requests. for example: foo.tld/bar?x=y to www.foo.tld/bar?x=y
if request.host !~ "^www\.(.*)$" {
redirect "." => "http://www.%1/$0?%{request.query}";
}
redirect.debug (option)
enable debug output
redirect.debug value;
mod_rewrite
mod_rewrite modifies request path and querystring
It supports matching regular expressions and substitution with captured substrings as well as other placeholders.
If your rewrite target does not contain any question mark (?
), then the querystring will not be altered.
If it does contain ?
the querystring will be overwritten with the part after the ?
. To append the original querystring, use %{request.query}
.
IMPORTANT: rewrite only changes the url, not the physical filename that it got mapped to by docroot or alias actions. So you need your docroot and alias actions after the rewrite.
If you have conditional rewrites like if !phys.is_file { rewrite ... }
you need docroot/alias both before (so it can check for the physical file) and after it (to build the new physical path).
rewrite (action)
modify request path and querystring
rewrite rule;
- rule
- a simple target string or one rule, mapping a regular expression to a target string, or a list of rules.
- The target string is a pattern.
- The regular expressions are used to match the request path (always starts with “/” and does not include the query string!)
- If a list of rules is given, rewrite stops on the first match.
- Replaces the query string iff the target string contains an
?
Example: rewrite always
setup {
module_load "mod_rewrite";
}
rewrite "/new/path";
Example: rewrite if match
setup {
module_load "mod_rewrite";
}
rewrite "regex" => "/new/path";
Example: rewrite on first match
setup {
module_load "mod_rewrite";
}
rewrite ("regex1" => "/new/path1", ..., "regexN" => "/new/pathN");
Example: pretty urls
Note: you really should move the logic for such rewrites into your application, and just rewrite all pretty urls to your index.php without putting the actions into the querystring.
setup {
module_load "mod_rewrite";
}
# bad: routing login in webserver config:
rewrite (
"^/article/(\d+)/.*$" => "/article.php?id=$1",
"^/download/(\d+)/(.*)$" => "/download.php?fileid=$1&filename=$2"
);
rewrite "^/user/(.+)$" => "/user.php?name=$1";
# good: handle it in the backend (easier to port the config)
rewrite "^/(article|download|user)(/|$)" => "/index.php";
rewrite_raw (action)
modify request path and querystring, matching and writing raw path
rewrite_raw rule;
- rule
- a simple target string or one rule, mapping a regular expression to a target string, or a list of rules.
Similar to rewrite
, but matches the raw path (i.e. the path before URL decoding and sanitizing) and the result is decoded again.
rewrite
writes the result to request.path
and possibly request.query
and uses URL encoding to generate request.raw_path
from those.
rewrite_raw
writes request.raw_path
and decodes it into request.path
and request.query
; this means the query string is always overwritten.
In both cases request.path
gets simplified afterwards.
rewrite.debug (option)
enable debug output
rewrite.debug value;
mod_scgi
mod_scgi connect to SCGI backends for generating response content
scgi (action)
connect to SCGI backend
scgi socket;
- socket
- socket to connect to, either "ip:port" or "unix:/path"
Example
setup {
module_load "mod_scgi";
}
if req.path =^ "/RPC2" {
scgi "127.0.0.1:5000";
}
mod_status
mod_status displays a page with internal statistics like amount of requests (total or per second), active connections etc.
status.css (option)
defines the stylesheet to use. available: unset (default), "blue" or any url you wish
status.css url;
Example
status.css = "blue";
status.info (action)
returns the status page to the client
status.info mode;
- mode
- (optional) "short"
The “short” mode removes connection and runtime details (recommended for “public” status).
The status page accepts the following query-string parameters:
-
?mode=runtime
: shows the runtime details -
format=plain
: shows the “short” stats in plain text format
Example
If /server-status is requested, a page with lighttpd statistics is displayed.
setup {
module_load "mod_status";
}
if req.path == "/server-status" {
status.info;
}
mod_throttle
mod_throttle limits outgoing bandwidth usage
All rates are in bytes/sec. The magazines are filled up in fixed intervals (compile time constant; defaults to 200ms).
io.throttle (action)
set the outgoing throttle limits for current connection
io.throttle (rate, burst);
- rate
- bytes/sec limit
- burst
- optional, defaults to 2*rate
burst
is the initial and maximum value for the magazine
; doing IO drains the magazine
, which fills up again over time with the specified rate
.
io.throttle_pool (action)
adds the current connection to a throttle pool for outgoing limits
io.throttle_pool rate;
- rate
- bytes/sec limit
all connections in the same pool are limited as whole. Each io.throttle_pool
action creates its own pool.
Example
Using the same pool in more than one place:
setup {
module_load "mod_throttle";
}
downloadLimit = {
io.throttle_pool 1mbyte;
}
# now use it wherever you need it...
downloadLimit;
io.throttle_ip (action)
adds the current connection to an IP-based throttle pool for outgoing limits
io.throttle_ip rate;
- rate
- bytes/sec limit
all connections from the same IP address in the same pool are limited as whole. Each io.throttle_ip
action creates its own pool.
Example
Using the same pool in more than one place:
setup {
module_load "mod_throttle";
}
downloadLimit = {
io.throttle_ip 200kbyte;
}
# now use it wherever you need it...
downloadLimit;
mod_userdir
mod_userdir allows you to have user-specific document roots being accessed through http://domain/~user/
The document root can be built by using the home directory of a user which is specified by /~username/ at the beginning of the request path.
Alternatively, mod_userdir can build the docroot from a pattern similar to vhost.pattern
but using the username instead of the hostname.
userdir (action)
builds the document root by replacing certain placeholders in path with (parts of) the username.
userdir path;
- path
- the path to build the document root with
If path
does not start with a slash (/), then the document root is prepended with the home directory of the given user as specified in /etc/passwd.
Otherwise the path
specifies the absolute docroot to be used.
Placeholders are:
-
*
is replaced by the complete username -
$1
-$9
are replaced by the n-th letter of the username, e.g.$2
is the second letter
Examples:
Request for http://host/~lighty/foo.html (assuming “/home/lighty” is the home of the “lighty” user):
path |
docroot | physicalpath |
---|---|---|
“public_html” | /home/lighty/public_html/ | /home/lighty/public_html/foo.html |
“/usr/web/*/” | /usr/web/lighty/ | /usr/web/lighty/foo.html |
“/usr/web” | /usr/web/lighty/ | /usr/web/lighty/foo.html |
“/www/users/$1/$1$2/*/” | /www/users/l/li/lighty/ | /www/users/l/li/lighty/foo.html |
Note: username “root” is not allowed for security reasons.
Example
setup {
module_load "mod_userdir";
}
userdir "public_html";
mod_vhost
mod_vhost offers various ways to implement virtual webhosts
It can map hostnames to actions and offers multiple ways to do so.
These ways differ in the flexibility of mapping (what to map and what to map to) as well as performance.
vhost.map (action)
maps given hostnames to action blocks
vhost.map mapping;
- mapping
- key-value list with hostnames as keys and actions as values
vhost.map
offers a fast (lookup through hash-table) and flexible mapping, but maps only exact hostnames (no pattern/regex matching). The server port is never considered part of the hostname. Use the key default
(keyword, not as string) to specify a default action.
Example
vhost.map ["host1" => actionblock1, "host2" => actionblock2, ..., "hostN" => actionblockN, default => actionblock0];
vhost.map_regex (action)
maps matching hostname patterns to action blocks
vhost.map_regex mapping;
- mapping
- key-value list with regular expressions for hostnames as keys and actions as values
vhost.map_regex
walks through the list in the given order and stops at the first match; if no regular expression matched it will use the default
action (if specified).
Example
vhost.map_regex ["host1regex" => actionblock1, "host2regex" => actionblock2, ..., "hostNregex" => actionblockN, default => actionblock0];
vhost.debug (option)
enable debug output
vhost.debug value;
Example
Combining both actions is also possible.
What it does (for example):
- if
example.com
orwww.example.com
is requested, the block named “examplesite” will be executed - if
mydomain.tld
is requested, the block named “mydomain” will be executed - if
mydomain.com
orwww.mydomain.net
is requested, the block named “mydomain” will be executed (through the regex matching) - if
www.mydomain.tld
or something entirely different is requested, the block named “defaultdom” will be executed
setup {
module_load "mod_vhost";
}
examplesite = {
#... more config here ...
};
mydomain = {
#... more config here ...
};
defaultdom = {
#... more config here ...
};
vhost.map [
"example.com" => examplesite,
"www.example.com" => examplesite,
"mydomain.tld" => mydomain,
default => {
vhost.map_regex [
"^(www\.)?mydomain\.(com|net|org)$" => mydomain,
default => defaultdom
];
}
];