POUND(8)

๐Ÿ’… POUND(8) โ€” pound (System Manager's Manual), bestie edition

๐ŸŽ€ NAME ๐ŸŽ€

pound โ€” HTTP/HTTPS reverse-proxy and load-balancer. Thatโ€™s the whole vibe: it catches requests up front and shares them out to your backends. ๐Ÿ’–

โœจ SYNOPSIS โœจ

Okay so hereโ€™s the command-line fit check, exactly as the page spells it:

pound  [-FVcehv] [-W [no-]FEATURE] [-f CONF-FILE] [-p PID-FILE]

Yep, thatโ€™s the whole synopsis. No cap. ๐Ÿซถ


๐ŸŒธ DESCRIPTION ๐ŸŒธ

Bestie, pound is a reverse-proxy load balancing server. It accepts requests from HTTP/HTTPS clients and distributes them to one or more Web servers (backends). Those requests may be passed to backends as plain HTTP or re-encrypted as HTTPS. Iconic. โœจ

If more than one backend server is defined, pound chooses one of them randomly, based on the requested balancing algorithm and backend priorities. By default, pound keeps track of associations between clients and backend servers (sessions). ๐Ÿ’…

๐ŸŒˆ GENERAL PRINCIPLES ๐ŸŒˆ

In general pound needs three types of objects defined in order to function: listeners, services and backends. Think of it as the holy trinity of the config, fr fr. ๐Ÿงš

๐ŸŽง Listeners

A listener is a definition of how pound receives requests from the clients (browsers). Listeners of two types are supported: regular HTTP listeners and HTTPS listeners. In the simplest case, a listener must define the address and port to listen on, with additional requirements for HTTPS listeners. Lowkey straightforward, highkey important. ๐ŸŒŸ

๐Ÿฆ‹ Services

A service routes requests to backends. Services may be defined within a listener or in global scope. When a request is received, pound attempts to match it to each service in turn, starting with the services defined in the listener itself and, if needed, continuing with those defined at the global level. Services may define their own conditions as to which requests they can answer: typically this involves certain URLs (e.g. images only, or a certain path) or specific headers (such as the Host header). A service may also define a session mechanism: if so, future requests from a given client will always be answered by the same backend. Main character energy for real. ๐Ÿ‘‘

๐Ÿงธ Backends

Backends define where to obtain the content requested. Most often, they refer to โ€œrealโ€ web servers and define how to contact them. ๐Ÿ“

Several types of backends are defined:

๐Ÿงท Regular backend

Regular backends are the most often used type of backends and the raison dโ€™etre of pound. A regular backend passes received requests to a backend server, waits for it to respond and sends the response back to the requesting client. Reliable king behavior. ๐Ÿ’š

๐Ÿšจ Emergency backend

A special case of regular backend which will be used only if all other regular backends fail to respond. Break-glass bestie. ๐Ÿ”ฅ

โ†ช๏ธ Redirect backend

A redirect backend responds to each request with a redirect response. See Redirect, below. ๐Ÿซถ

๐Ÿงพ ACME backend

A special backend designed to handle ACME challenges. Backends of this type are used to handle HTTP-01 authorization when re-issuing LetsEncrypt certificates.

See the ACME section below. ๐ŸŒฑ

๐Ÿ’€ Error backend

The error backend creates and returns a predefined HTTP status response. Itโ€™s giving โ€œnope, and hereโ€™s the receipt.โ€ ๐Ÿ˜ญ

๐Ÿ“„ Static file backend

This backend responds with a contents of a disk file. Simple, clean, slay. ๐Ÿ’…

Multiple regular or emergency backends may be defined within a service, in which case pound will balance the load between the available backends. โš–๏ธ

Two types of load-balancing strategies are implemented: random balancing (default), and interleaved weighted round-robin balancing. Choices, choices. โœจ

If a regular backend fails to respond, it will be considered โ€œdeadโ€, in which case pound will stop sending requests to it. Dead backends are periodically checked for availability, and once they respond again they are โ€œresurrectedโ€ and requests are sent again their way. If no backends are available (none were defined, or all are โ€œdeadโ€), then pound will reply with 503 Service Unavailable, without checking additional services. Dramatic? Maybe. Accurate? Absolutely. ๐Ÿ’€

Normally, pound communicates with its backends via plain HTTP. It is, however, possible to use HTTPS as well. ๐Ÿ”

A working pound configuration may define multiple listeners and services. They can be identified either by 0-based ordinal number within the configuration (or, for services, within the listener) or by their symbolic name. Backends are identified by their ordinal number within the service. Numbers and names, bestie โ€” know the difference. ๐ŸŽ€

๐ŸŽก REQUEST BALANCING ๐ŸŽก

Load balancing strategy defines algorithm used to distribute incoming requests between multiple regular backends. Each backend is assigned a priority โ€” a positive number indicating its relative weight among other backends. The share of requests a backend handles can be estimated as:

Pi / S(P)

where Pi is priority of the backend with index i, and S(P) is sum of all priorities. Math but make it fashion. ๐Ÿงฎ

Two balancing strategies are implemented:

๐ŸŽฒ Weighted Random Balancing

This is the default strategy. The backend to use for each request is determined at random taking into account backend priorities, so that backends with numerically greater priorities have proportionally greater chances of being selected than the ones with lesser priorities. RNG with a weighted little twist. ๐Ÿ“

๐Ÿ” Interleaved Weighted Round Robin Balancing

This strategy cycles over all active backends, considering each one in its turn. An integer ordinal number is assigned to each round, which is incremented (modulo number of backends) each time a new round is started. A backend is assigned a request only if its priority is greater than the round number. Round and round and round we go. ๐ŸŒ€

This strategy offers several advantages compared with the previous one. First, it results in a more even distribution of the requests. Secondly, the resulting distribution is predictable. Predictable is such a flex. โœจ

Within each Service, multiple backends are grouped in balancer groups. Each such group is assigned a unique integer priority, which defines the order in which the groups are tried. When selecting a backend, a group with the numerically lesser priority is selected and a suitable backend is looked up among backends defined in the group, using one of the balancing strategies discussed above. If no backend can be selected (e.g. all backends are unavailable), next group is tried. The process continues until either a backend is chosen or the list of balancer groups is exhausted. Group chat hierarchy, but make it infrastructure. ๐Ÿ’…

By default, backends declared using the Backend keyword are assigned to the balancer group 0. These are backends used during normal operation. Backends declared using the Emergency keyword are assigned to the balancer group 65535. These backends form a pull of high availability backends, which will be tried only if all of the normal backends fail. Break-glass group has entered the chat. ๐Ÿšจ

More backend groups can be added using dynamic backends, discussed below. ๐ŸŒˆ

๐Ÿงต WORKER MODEL ๐Ÿงต

Each incoming connection is assigned to a specific worker, i.e. a thread in the running program. This worker processes all requests that arrive via that connection. One connection, one worker, no sharing drama. ๐Ÿซถ

The number of running workers is controlled by three configuration parameters. WorkerMinCount defines the minimum number of workers that should always be running (5, by default). Another parameter, WorkerMaxCount sets the upper limit on the number of running workers (it defaults to 128). Minimum vibe, maximum vibe. ๐Ÿ’–

At each given moment, a worker can be in one of two states: idle or active (handling a connection). If an incoming connection arrives when all running workers are active, and total number of workers is less than WorkerMaxCount, a new worker is started and the new connection is handed to it. If the number of active workers has already reached maximum, the new connection is added to the connection queue, where it will wait for a worker to become available to process it. If the queue becomes full as well, the program stops accepting new connections until at least one worker becomes available again. Queue full = no new friends right now. ๐Ÿ˜ญ

The third parameter, WorkerIdleTimeout, specifies maximum time a thread is allowed to spend in the idle state. If a worker remains idle longer than that and total number of workers is greater than the allotted minimum (WorkerMinCount), the idle worker is terminated. Idle time is over, bestie. ๐Ÿ’€

The capacity of the connection queue is computed so as to accept as many connection requests as is permitted by the current system limit on the open files. It can also be set using the ConnectionQueueSize configuration directive. Queue math: handled. โœ…

โœจ OPTIONS โœจ

okay bestie, here's the command line menu ๐Ÿ’… these are all the flags you can throw at pound:

  • -c โ€” check only! pound exits immediately after parsing the configuration file. literally perfect for running a quick syntax check before you actually activate a server ๐Ÿซถ
  • -e โ€” log to standard error (standard output for LOG_DEBUG and LOG_INFO severity levels). both standard streams get set to line buffering mode. this option implies foreground mode (-F) and overrides the LogLevel configuration setting. lowkey a two-for-one ๐Ÿ˜ญ
  • -F โ€” foreground mode. the program will not detach from the controlling terminal and will remain in foreground after startup. this overrides the Daemon configuration setting. the log stream (syslog facility or stderr) requested in the configuration remains in effect. see also the -e option, above ๐Ÿ’–
  • -f FILE โ€” location of the configuration file (see below for a full description of the format). default is $sysconfdir/pound.cfg, where $sysconfdir stands for the system configuration directory, as determined at build time. most often it is either /usr/local/etc, or /etc. slay ๐ŸŽ€
  • -h โ€” print short command line usage summary and exit. the "help me bestie" flag โœจ
  • -p pid_file โ€” location of the PID file. pound will write its own PID into this file. normally this is used for shell scripts that control starting and stopping of the daemon. see the description of the PIDFile statement in the GLOBAL DIRECTIVES section below, for a detailed discussion of this file โ˜•
  • -v โ€” verbose mode: during startup, error messages will be sent to stderr (stdout for LOG_DEBUG and LOG_INFO severity levels). if pound was configured to log to syslog, error diagnostics will be duplicated there as well. after startup the configuration settings take effect. nosy but make it useful ๐Ÿ™ˆ
  • -V โ€” print version: pound will exit immediately after printing the current version, licensing terms, and configuration flags. like a little business card ๐Ÿ‘‘
  • -W [no-]FEATURE โ€” enable or disable (if prefixed with no-) additional pound features. as of this version, the following FEATUREs are implemented:
    • [no-]warn-deprecated โ€” warn if any deprecated statements are used in configuration file. your fave little "girl what are you doing" alarm ๐Ÿ’€
    • [no-]dns โ€” resolve host names found in configuration file. this is the default. you can disable it if your configuration file refers to backends only by their IP addresses, in order to suppress potentially lengthy network host address lookups. bye bye waiting around ๐Ÿฆ‹
    • no-include-dir โ€” don't set the include directory to the system configuration directory. this means that each relative filename used in arguments to the directives in the configuration file will be looked up in the current working directory. this feature is useful mainly in testsuite. iykyk ๐Ÿงš
    • include-dir=DIR โ€” override the default include directory setting. set it to DIR. see the discussion of the IncludeDir directive in section GLOBAL DIRECTIVES, below ๐ŸŒธ

๐Ÿ“ CONFIGURATION FILE ๐Ÿ“

each line in the file is considered a complete configuration directive. empty lines and comments are ignored โ€” as they should be ๐Ÿ’…. comments are introduced with a # sign and extend to the end of line on which it appears.

there are three types of directives, and yes they all have main character energy:

  • global directives โ€” they affect the settings for the entire program instance ๐ŸŒ
  • listener directives โ€” they define which requests pound will listen for ๐Ÿ‘‚
  • service directives โ€” they affect only a specific group of requests ๐ŸŽฏ

in general, a directive consists of a keyword and one or more values, separated by any amount of whitespace. leading and trailing whitespace is ignored. keywords are case-insensitive (so no stress about caps, bestie โœจ). a value can be:

  • Numeric โ€” a decimal number. ๐Ÿ”ข
  • Boolean โ€” the words yes, true, on, or 1 indicating true, and no, false, off, or 0 indicating false. all words are case-insensitive. no cap ๐Ÿ™Œ
  • String โ€” any sequence of characters between double-quotes. a backslash is treated as an escape character: if it is followed by a double-quote or another backslash, it is removed and the character after it is read literally. if it is followed by any other character, a warning message is printed. she's a little dramatic like that ๐Ÿ˜ญ
  • Identifier โ€” a sequence of characters starting with an ASCII letter and consisting of letters, digits and underscores ๐Ÿ’–
  • IP address โ€” an IPv4 or IPv6 address in numeric form, or a hostname ๐ŸŒ

unless specified otherwise, directives may appear in any order. freedom, honestly ๐Ÿฆ‹


๐Ÿ‘‘ GLOBAL DIRECTIVES ๐Ÿ‘‘

global directives may appear anywhere at the top level within the configuration file, although it is customary for them to be at the start. aesthetic, y'know ๐ŸŽ€

๐Ÿ’ซ Program settings ๐Ÿ’ซ

  • Daemon bool โ€” instructs pound to run in the foreground (if false) or as a daemon (if true). by default pound runs as a daemon (i.e. detaches itself from the controlling terminal and continues operating in the background). by setting this directive to false you can force pound to work like a regular process. see also -F and -e options, above. choose your fighter โœจ
  • Balancer random | iwrr โ€” defines the load-balancing strategy to use. possible arguments are: random, to use weighted random balancing algorithm, and iwrr, meaning interleaved weighted round robin balancing. see REQUEST BALANCING, above, for a detailed discussion of these balancing strategies ๐Ÿ”ฅ

the Balancer statement in the global scope applies to all Service directives that don't contain Balancer definitions of their own. so it's like the default outfit and the services can change if they want ๐Ÿ’…

  • Constant "NAME" [OPTIONS] ["VALUE"] โ€” defines a configuration constant for use in string expansions and Lua code. NAME gives the name of the constant. either OPTIONS or VALUE must be given, but not both. pick one, bestie ๐Ÿ™ˆ

if OPTIONS are not given, VALUE supplies the value for the constant. otherwise, the value is assigned as described in the table below:

  • -file "FILENAME" โ€” use contents of the file "FILENAME". the contents is treated as an opaque binary string. the file is read exactly once, at program startup. one and done ๐Ÿ“
  • -filewatch "FILENAME" โ€” same as -file, except that FILENAME is monitored for changes during the runtime. whenever the file changes, it will be re-read, and the constant will be updated, without the need to restart the program. she stays updated fr fr ๐Ÿ‘€
  • -trim โ€” used with one of the above, instructs pound to treat FILENAME as a plain text file, and remove from its contents all trailing whitespace characters, i.e. horizontal tabulation (ASCII 9), newline (ASCII 10), vertical tabulation (ASCII 11), form feed (ASCII 12), carriage return (ASCII 13), and space (ASCII 32). the cleanup queen ๐Ÿงน
  • ConnectionQueueSize "N" โ€” set the capacity of the incoming connection queue. the default value is computed as a function of WorkerMaxCount and the system limit on open file descriptors. see the description in WORKER MODEL, and that of the ReserveFD statement, below. math but make it slay ๐Ÿ”ข
  • Control "pathname" โ€” set the control socket path. see the Control socket section below, for a detailed description of this feature ๐ŸŽ›๏ธ
  • IgnoreCase bool โ€” ignore letter case when doing regex matching (default: false). this directive sets the default for the following service matching directives: URL, Path, QueryParam, Query, StringMatch, as well as for the DeleteHeader modification directive. its value can be overridden for specific services ๐Ÿ”ค
โš ๏ธ this statement is deprecated and will be removed in future versions. please, use the -icase option to the service matching directive instead. see the discussion of options in Service Matching Directives section below. don't say i didn't warn you ๐Ÿ’€
  • LineBufferSize N โ€” sets the line buffer size. line buffer is used when reading request line, backend response status line, and header fields in both requests and responses. thus, this value limits the maximum length allowed for these parts of HTTP messages. by default, it is set to the internal buffer size (4096 bytes, configurable at compile time using the --with-maxbuf option). it cannot be set to a value less than that default. so 4096 is the floor, periodt ๐Ÿšซ
  • PIDFile "filename" โ€” sets the name of the file where to store program PID. it can be overridden by the -p command line option.

notice the following:

  1. when running with a supervisor, this file holds PID of the supervisor process. otherwise, it holds PID of the main pound process. this means it is always suitable for signalling the program using the traditional kill-with-cat technique:
kill `cat filename`
  1. before shutting down, pound removes this file. however, this may not be possible if it switches to privileges of another user after startup (i.e. at least one of User or Group are set in the configuration file) and the file is stored in a directory whose permissions forbid write access for that user. so plan your permissions, bestie ๐Ÿ˜ญ
  • RegexType posix | pcre | perl โ€” sets the type of regular expressions to use in request matching statements. posix selects POSIX extended regular expressions and pcre or perl select Perl-compatible regular expressions. the latter requires compile-time support. the selected regular expression type remains in effect until next RegexType statement or end of the configuration file, whichever occurs first ๐Ÿช„
  • ReserveFD [-worker] N โ€” reserve N additional file descriptors for the purpose of computing the capacity of the incoming connection queue. if the -worker option is given, increases number of file descriptors per worker thread (default value: 2). otherwise, increases number of reserved global descriptors (default value: 3) ๐Ÿ™Œ
  • Supervisor bool โ€” when running in daemon mode, start a supervisor process first. this process will monitor the subordinate pound process, restarting it if it fails. she's got a babysitter ๐Ÿ‘‘
  • Threads N โ€” this statement, retained for backward compatibility with previous versions of pound, is equivalent to:
WorkerMinCount N
WorkerMaxCount N

oldie but a goodie ๐Ÿซถ

  • WorkerIdleTimeout SEC โ€” how long a worker thread is allowed to sit around doing nothing before it gets dropped ๐Ÿ˜ด default is 30 seconds. The whole saga is in WORKER MODEL above if you want the lore.
  • WorkerMinCount N โ€” the minimum number of worker threads that are always running, no matter what ๐Ÿซถ default is 5. Again, WORKER MODEL above has the full breakdown.
  • WorkerMaxCount N โ€” the ceiling, bestie. Max number of worker threads ever ๐Ÿšฆ default is 128. Yep, WORKER MODEL above, go read it.
  • WorkerStackSize N โ€” worker stack size, in bytes ๐Ÿ“ default is 262144.

๐ŸŽ€ Privilege selection ๐ŸŽ€

Like, who even are you, pound? This is where you tell it ๐Ÿ‘‘

  • User "user_name" โ€” pick the user pound runs as. It must already exist in the system user database, no cap ๐Ÿ”
  • Group "group_name" โ€” same vibe, but for the group. It must be defined in the system group database ๐Ÿ’…
  • RootJail "directory" โ€” tells pound which directory to chroot to at runtime ๐Ÿ”’ iconic security behavior, fr.

๐ŸŒธ SSL engine settings ๐ŸŒธ

  • SSLEngine "name" โ€” use an OpenSSL hardware acceleration card called name โšก heads up though: this is only available if OpenSSL-engine is installed on your system, so vibe check that first.

๐Ÿฆ‹ Logging ๐Ÿฆ‹

The part where pound tells you everything that happened ๐Ÿ’ฌ

  • Anonymise โ€” (alt spelling Anonymize is also accepted, we're flexible ๐Ÿ’) replaces the last byte of the client address with 0 for logging purposes. Default behavior: log the client address in full. Privacy moment โœจ
  • LogFacility ident โ€” which log facility to use. ident is one of these, and yes you have to spell them right:

auth, authpriv, cron, daemon, ftp, kern, lpr, mail, news, syslog, user, uucp, local0 through local7.

Default value is daemon ๐Ÿ‘ป and plot twist: if you use a - (dash) for the facility name, pound logs to stdout/stderr instead, which is lowkey great for debugging.

  • LogFormat "name" "format_def" โ€” define your own HTTP log format ๐Ÿ’– name is a string that uniquely identifies this format, and format_def is the format string definition. Detailed breakdown lives below in REQUEST LOGGING, so scroll down bestie.
  • LogLevel n โ€” set the logging level using the built-in format indices ๐ŸŽš๏ธ:
    • 0 โ€” no logging at all (silence, luxury)
    • 1 โ€” regular logging (this is the default)
    • 2 โ€” extended logging (shows the chosen backend server as well)
    • 3 โ€” Apache-like format (Combined Log Format with Virtual Host)
    • 4 โ€” same as 3 but without the virtual host information
    • 5 โ€” same as 3 but with info about the Service and Backend used

This value can be overridden for specific listeners ๐Ÿ‘‚ And yeah, REQUEST LOGGING below has the full description.

  • LogLevel "name" โ€” pick a named format for logging HTTP requests instead of a number ๐Ÿ’ซ Name can be either one of the five built-in format names โ€” null, regular, extended, vhost_combined, combined, or detailed โ€” matching the numeric indices we just talked about, or a format name you defined earlier with LogFormat. See REQUEST LOGGING for the deep dive.
  • LogTag "string" โ€” sets the string used to tag log messages ๐Ÿท๏ธ used when log output goes to syslog. Default is the name pound was started with.
  • ForwardedHeader name โ€” defines the name of the HTTP header that carries the list of proxies the request has passed through ๐Ÿ“ฌ it's used to report the originator IP address when logging. See the %a specifier description in REQUEST LOGGING. Default is X-Forwarded-For.
  • TrustedIP โ€” defines a list of trusted proxy IP addresses, used to determine the originator IP ๐Ÿ‘€ see the %a specifier description in REQUEST LOGGING for the detailed discussion.

This statement is a special form of ACL statement, described below. It can appear as an anonymous ACL (section), a file reference, or as an ACL reference. In anonymous ACL form, it's followed by a list of one or more CIDRs, each on its own line. The End keyword terminates the statement, like this:

TrustedIP
  "127.0.0.1/8"
  "10.16.0.0/16"
End

In file reference form, the statement looks like:

TrustedIP -file "filename"

or

TrustedIP -filewatch "filename"

and the list of IP addresses gets read from the file filename ๐Ÿ“‚ when you use -filewatch, the file is watched for changes and reloaded when they happen ๐Ÿ‘๏ธ

In ACL reference form, this statement takes a single argument โ€” the name of the access control list defined earlier with the ACL statement, like:

TrustedIP "proxy_addresses"

๐Ÿ’œ Timeouts ๐Ÿ’œ

Everything in here is about pound's patience level. It has some, but not infinite ๐Ÿซ 

  • Alive n โ€” how often pound checks for resurrected backend hosts ๐Ÿ‘ป default: 30 seconds. Generally it's a good idea to set this as low as possible โ€” it'll find resurrected hosts faster. But bestie, beware: set it too low and it'll consume resources ๐Ÿ˜ญ so, like, balance.
  • Client n โ€” how long pound waits for a client request โณ default: 10 seconds. After that long with the client sending no data, pound closes the connection. Bump it higher if your clients time out on a slow network or an overloaded server. Bump it lower if you start getting DOS attacks or run into problems with IE clients ๐Ÿ’€ This value can be overridden for specific listeners.
  • ConnTO n โ€” how long pound should wait for a connection to the backend, in seconds ๐Ÿ”Œ default: the TimeOut value. This value can be overridden for specific backends.
  • Grace n โ€” how long pound keeps answering existing connections after receiving an INT or HUP signal ๐Ÿ•Š๏ธ default: 30 seconds. The configured listeners are closed immediately. You can bypass this behavior entirely by stopping pound with a TERM or QUIT signal โ€” in that case the program exits without any delay ๐Ÿƒโ€โ™€๏ธ๐Ÿ’จ
  • TimeOut n โ€” how long pound waits for a response from the backend, in seconds โฑ๏ธ default: 15 seconds. This value can be overridden for specific backends.
  • WSTimeOut n โ€” how long pound waits for data from either the backend or the client on a connection upgraded to a WebSocket, in seconds ๐Ÿงฆ default: 600 seconds. This value can be overridden for specific backends.
  • WatcherTTL N โ€” if conditions with the -filewatch flag are used, and your underlying OS doesn't provide a file system monitoring API (inotify or kqueue), this directive defines the interval between two successive checks of file modification times ๐Ÿ‘€ (yes, it's polling, we know, it's doing its best).

๐ŸŒˆ Header manipulation ๐ŸŒˆ

  • CombineHeaders ... End โ€” declare the names of headers that can appear multiple times in a message and should be combined into one value ๐Ÿงต header names go one per line between CombineHeaders and End. See MULTI-VALUE HEADERS for the detailed discussion of this feature.
  • HeaderOption opt... โ€” sets the default options for header addition ๐ŸŽ›๏ธ opt is one of:
    • none โ€” disable additional headers
    • forwarded โ€” enable adding X-Forwarded-For, X-Forwarded-Proto, and X-Forwarded-Port headers
    • ssl โ€” enable passing information about SSL certificates in various X-SSL-* headers

The option may be prefixed with no-, which disables the corresponding header group ๐Ÿšซ The default is:

HeaderOption forwarded ssl

This setting can be overridden on a per-listener basis. See the HeaderOption directive description in the HTTP Listener section, plus BUILT-IN HEADERS, for the detailed discussion of the various header modification directives and their effects ๐Ÿ’ซ

๐Ÿงš File inclusion ๐Ÿงš

  • IncludeDir "dir" โ€” sets the include directory ๐Ÿ“ this is where pound looks for relative file names that appear in other configuration directives (e.g. Include). The default value is the system configuration directory as set at compile time โ€” you can check its value in the output of pound -V ๐Ÿ” This initial value can be changed on the command line using the -W include-dir=name option, or reset to the current working directory using the -W no-include-dir option (see the discussion of -W below).

The directory set by the IncludeDir statement remains in effect until the next IncludeDir statement is encountered, or end of input โ€” whichever happens first ๐Ÿ

  • Include "file" โ€” include the file as though it were part of the configuration file ๐Ÿ“Ž if file is a relative file name, it'll be looked up in the include directory (see above). If file is a shell globbing pattern (glob(7)), all regular files matching that pattern get included. The list of files will be sorted lexicographically ๐Ÿ”ค

This directive is allowed both at the topmost level and in any subsections of the configuration file. Versatile icon ๐Ÿ’…

โญ Access control lists โญ

  • ACL "name" โ€” define a named access control list (ACL) ๐Ÿ” an ACL is a list of network addresses in CIDR notation, one address per line, terminated with an End directive on a line by itself. Like so:
ACL "secure"
   "192.0.2.0/26"
   "203.0.113.0/24"
End

The Include directive is allowed within an ACL ๐Ÿซถ

Named ACLs can be used in Service definitions to make services available from certain IP addresses only ๐Ÿ‘‘

  • ACL "name" -file "filename" โ€” same as above, but reads the ACL definition from the file filename ๐Ÿ“„ the name is taken relative to the IncludeDir directory, unless it starts with a slash.

The ACL file should contain one CIDR per line. Empty lines and comments are allowed โœ…

  • ACL "name" -filewatch "filename" โ€” same as above, but filename gets watched for changes during runtime ๐Ÿ‘๏ธ once a change is detected, the file is reloaded and the ACL updated ๐Ÿ”„

If possible, this uses an OS-specific API (inotify(7) on GNU/Linux and kqueue(7) on *BSD systems). On systems that don't provide such an API, the file is checked for changes periodically โฐ the interval between two successive checks is defined by the WatcherTTL setting.

๐ŸŽ€ Detached conditions ๐ŸŽ€

Condition "NAME"
    ...
End

Saves away a set of conditional statements for further evaluation ๐Ÿ’พ the ellipsis represents a sequence of conditional statements as described in section Service Matching Directives. The detached condition can be used wherever a normal conditional is allowed, using this construct:

Eval "NAME"

Detached conditions are evaluated at most once ๐Ÿง  the result of the evaluation is cached and used by all subsequent Eval statements that refer to the same NAME. Efficiency ate, fr fr ๐Ÿ”ฅ

๐ŸŽ€ Control socket ๐ŸŽ€

okay so bestie, pound can also be told to listen for management requests โ€” like, it'll open up a whole little admin channel so you can peek at info about the running instance, change the state of configured listeners, services, and backends, that kind of thing. these requests get issued with the poundctl(8) utility. ๐Ÿ’…

normally it's a UNIX socket that talks to the management interface, and you set it up with the Control statement. this statement comes in two forms, bestie. โœจ

in inline form, it takes a single argument โ€” the name of the UNIX socket file to create and listen on. for example:

Control "/run/pound.sock"

the file will be owned by the user that started pound (normally root) and will have mode 0600. ๐Ÿ”’

the block form lets you specify the file mode and, to a certain extent, the socket file ownership:

Control
    Socket "/run/pound.sock"
    Mode 660
    ChangeOwner true
End

the substatements are:

  • Socket filename โ€” specifies the name of the socket file to use. this is the only mandatory statement in the block form. ๐Ÿ‘‘
  • Mode octal โ€” sets the mode of the socket file.
  • ChangeOwner bool โ€” this one only kicks in if at least one of the User or Group global statements is used. when set to true it changes the owner of the socket file to whatever those two statements say. ๐Ÿซถ

it's also possible to have pound listen for management requests on an INET or INET6 address โ€” see the description of the Control statement in section Backend definitions, below. ๐ŸŒˆ


๐ŸŒธ HTTP Listener ๐ŸŒธ

an HTTP listener defines an address and port that pound will listen on for HTTP requests. the listener declaration begins with the keyword ListenHTTP on a separate line. the keyword may be followed by a quoted string supplying the listener name โ€” that name is a unique label that identifies the listener. ๐Ÿท๏ธ

all configuration directives enclosed between ListenHTTP and End are specific to a single HTTP listener. at the very least you must specify the IP address and, optionally, the port for each listener. The following directives are available:

  • Address address โ€” the address pound listens on. this can be a numeric IP address, or a full pathname of a UNIX socket. if neither this directive nor SocketFrom (see below) is given, ::0 is assumed. ๐ŸŒ
  • Port port โ€” the port number or service name pound listens on. if not given, the default port number is assumed, i.e. 80, for ListenHTTP, and 443, for ListenHTTPS (see below). ๐Ÿ”ข
  • SocketFrom "pathname" โ€” read the socket to listen on from the UNIX socket given as argument. if this parameter is supplied, neither Address nor Port may be used. this directive is intended for testing pound. ๐Ÿงช
  • xHTTP n โ€” defines which HTTP verbs are accepted. the possible values are:
    • 0 (default) โ€” accept only standard HTTP requests (GET, POST, HEAD).
    • 1 โ€” additionally allow extended HTTP requests (PUT, PATCH, DELETE).
    • 2 โ€” additionally allow standard WebDAV verbs (LOCK, UNLOCK, PROPFIND, PROPPATCH, SEARCH, MKCOL, MOVE, COPY, OPTIONS, TRACE, MKACTIVITY, CHECKOUT, MERGE, REPORT).
    • 3 โ€” additionally allow MS extensions WebDAV verbs (SUBSCRIBE, UNSUBSCRIBE, NOTIFY, BPROPFIND, BPROPPATCH, POLL, BMOVE, BCOPY, BDELETE, CONNECT).
  • Client n โ€” override the global Client time-out value. โฑ๏ธ
  • CheckURL "pattern" โ€” define a pattern that every request sent to this listener must match. a request that doesn't match is considered illegal. by default pound accepts all requests (i.e. the pattern is ".*"), but you're free to limit it to something more reasonable. heads up: this applies only to the request path โ€” pound will still check that the request is syntactically correct. ๐Ÿ•ต๏ธ
  • Constant "NAME" [OPTIONS] ["VALUE"] โ€” defines a configuration constant for use in string expansions and Lua code. see Constant in section GLOBAL DIRECTIVES for the full breakdown of its arguments and what they mean. per-listener constants augment global ones. if the name of a per-listener constant coincides with the name of a globally defined one, the former overrides the latter for all services defined in that listener. โœจ
  • ErrorFile NNN "filename" โ€” read the content to be returned along with the HTTP status code NNN from the file filename. the file is read exactly once, at program startup. ๐Ÿ“„ the file can begin with one or more HTTP headers, separated from the actual body with an empty line (i.e., two newline characters). these headers get returned along with the response โ€” this can be used, in particular, to change the response content type, which is text/html by default. allowed values for NNN are: 400, 401, 404, 405, 413, 414, 500, 501, 503.
  • ErrNNN "filename" โ€” this statement is equivalent to
ErrorFile NNN "filename"

see above for the allowed values of NNN.

โš ๏ธ this form is provided for compatibility with previous versions of the program. it will be removed in future releases โ€” so maybe don't build your whole personality around it, bestie. ๐Ÿ’€
  • RewriteErrors bool โ€” if bool is true, this enables backend error response rewriting, i.e. replacing the content of error responses returned by a regular backend with a custom error text defined by the ErrorFile statement. see ERROR RESPONSE REWRITING for a detailed discussion of this feature. the same statement can also appear in a Service block, in which case it overrides the settings of the listener. ๐Ÿ”
  • LineBufferSize N โ€” sets the line buffer size. the line buffer is used when reading the request line, the backend response status line, and header fields in both requests and responses. the default is inherited from the global setting. ๐Ÿ“
  • MaxRequest n โ€” maximum allowed size of incoming request. all requests will be limited to these many bytes. if a request contains more data than allowed, an error 413 is returned. default: unlimited. ๐Ÿšซ
  • MaxURI n โ€” maximum allowed length of a URI. if the URI of a request is longer than n bytes, an error 414 is returned. default: unlimited. ๐Ÿ“
  • RewriteLocation 0|1|2 โ€” if set to 1, force pound to change the Location: and Content-location: headers in responses. if they point to the backend itself or to the listener (but with the wrong protocol), the response gets changed to show the virtual host in the request. default: 1 (active). if the value is set to 2, only the backend address is compared โ€” this is useful for redirecting a request to an HTTPS listener on the same server as the HTTP listener. ๐Ÿงญ
  • RewriteDestination bool โ€” if set to true, force pound to change the Destination: header in requests. the header is changed to point to the backend itself with the correct protocol. default: false. ๐ŸŽฏ
  • LogLevel n โ€” log HTTP requests using built-in format n. this statement configures logging specific for this listener, overriding the global LogLevel setting. see REQUEST LOGGING for a detailed discussion. ๐Ÿ“
  • LogLevel "name" โ€” select a named format for logging HTTP requests. name can be either one of five built-in format names (null, regular, extended, vhost_combined, combined, or detailed), or a format name defined earlier via the LogFormat directive. this statement configures logging specific for this listener, overriding the global LogLevel setting. see section REQUEST LOGGING for a detailed discussion. ๐Ÿ—‚๏ธ
  • ForwardedHeader name โ€” defines the name of the HTTP header that carries the list of proxies the request has passed through. it's used to report the originator IP address when logging. see the description of the %a specifier in REQUEST LOGGING. this statement overrides the ForwardedHeader directive from the global scope. the default is X-Forwarded-For. ๐Ÿ“จ
  • TrustedIP โ€” defines a list of trusted proxy IP addresses, which is used to determine the originator IP. see the description of the %a specifier in REQUEST LOGGING for a detailed discussion. this statement overrides the TrustedIP directive from the global scope. ๐Ÿ›ก๏ธ
  • Service [ "name" ] โ€” this defines a private service (see below for the service definition syntax). this service is used only by this listener. the optional name supplies the label that can be used in poundctl(8) requests to identify the service. this label must be unique among all services within the enclosing listener, or, for global services, within the configuration. ๐Ÿงš
  • ACME "directory" โ€” serve ACME challenge requests from the given directory. see section ACME below. ๐Ÿ”
  • HeaderOption opt... โ€” modifies global header addition options for this listener. global options are set by the HeaderOption directive in the global scope and default to forwarded ssl. opt is one of:
    • all โ€” enable all additional headers.
    • none โ€” disable all additional headers.
    • forwarded โ€” enable adding X-Forwarded-For, X-Forwarded-Proto, and X-Forwarded-Port headers.
    • ssl โ€” enable passing information about SSL certificates in various X-SSL-* headers.

each option except none can be prefixed with no- to revert its meaning. for example, to disable adding the X-SSL-* headers for a listener, you'd use:

HeaderOption no-ssl

see the description of the HeaderOption directive in the GLOBAL DIRECTIVES section, and section BUILT-IN HEADERS, for a detailed discussion of the various header modification directives and their effect. ๐ŸŽ€


โœจ String expansion โœจ

some of the statements described below take as their arguments string values that undergo several expansions before use. these expansions are as follows:

๐Ÿฆ‹ Backreference expansion ๐Ÿฆ‹

a backreference is a construct that refers to a parenthesized group within a regular expression matched by one of the service matching directives (see the section Service Matching Directives, below). during backreference expansion, each occurrence of such a construct in a string is replaced with the actual value of that parenthesized group. ๐Ÿ’–

and that's where this part leaves us, bestie โ€” the rest comes at you in the next chunk, no cap! ๐Ÿซถ

k so backreferences come in exactly two shapes, and bestie you're gonna want both memorized ๐Ÿ’… The construct $N (where N is a decimal number) points at the Nth parenthesized subexpression of the most recently matched statement, and $N(M) points at the Nth parenthesized subexpression of the Mth recently matched statement. Subexpression numbering starts at 1 โ€” and yes, $0 is special, it refers to the entire matching string ๐ŸŒŸ Numbering of matched statements starts at 0.

Conditional statements can also be tagged with the -tag T option. When you're referring to a subexpression inside a tagged statement like that, you use the $N(T) form. iykyk ๐ŸŽ€

For example, given the following statements:

Host -tag "host" -re "www\.(.+)"
Header -tag "type" -re -icase "^Content-Type: *(.*)"
Path -tag "dir" "^/static(/.*)?"

then $1 or $1(dir) refers to the subgroup of Path, $1(1) or $1(type) refers to that of Header, and $1(2) or $1(host) refers to that of Host. literally so satisfying when it clicks โœจ

Curly braces exist so you don't accidentally parse whatever text fragment follows the reference as if it were part of the reference. This is the move when the reference is immediately followed by a decimal digit or an opening parenthesis โ€” as in: "${1}(text)". ๐Ÿซถ

To insert a literal dollar or percent sign in url, use $$ or $%, correspondingly.

๐Ÿฆ‹ Request accessor interpretation ๐Ÿฆ‹

A request accessor is %[name], where name is the part of the incoming request you wanna peek at. Accessors get interpreted and swapped out for the corresponding piece of the actual request. Some accessors take an argument, and that argument goes after the accessor name, separated from it by one or more whitespace characters.

Here's the whole roster, no gatekeeping:

  • const NAME [ENCODE] โ€” the configuration constant NAME (see Constant in the section GLOBAL DIRECTIVES, above). The optional ENCODE says which encoding to represent it in, and its possible values are:
    • none โ€” don't encode it. This is the default when no ENCODE argument is specified.
    • url โ€” encode the value using percent-encoding (RFC 3986).
    • base64 โ€” encode the value using BASE64 encoding (RFC 3548).
  • url โ€” the request URL.
  • path โ€” the request path.
  • query โ€” the query part.
  • param NAME โ€” the value of the query parameter NAME.
  • header NAME โ€” the value of HTTP header NAME.
  • host โ€” the hostname part of the Host header. If the latter doesn't include a port number, this is equivalent to %[header host]. ๐Ÿ‘‘
  • port โ€” if the value of the Host header includes a port number, %[port] evaluates to that port number with the leading colon character. Otherwise it evaluates to an empty string.
  • remoteip FWD โ€” expands to the request originator IP address. If FWD is 0, it expands to the real IP; otherwise it expands to the IP determined from the X-Forwarded-For (or similar) header. ๐Ÿงš
  • user โ€” if the request bears the Authorization: Basic header, this expands to the user name extracted from its value. Otherwise its expansion is empty.

๐Ÿ’… Modification directives ๐Ÿ’…

The following directives modify the incoming request before it gets passed to the selected service. These get the full deep-dive treatment in section REQUEST MODIFICATION, below โ€” but here's the quick tour, bestie:

  • DeleteHeader [options] "pattern" โ€” removes certain headers from the incoming requests. All occurrences of the matching header will be removed, not just one, okay? The options define how to match pattern:
    • -beg โ€” exact match at the beginning of the header (prefix match).
    • -case โ€” case-sensitive comparison.
    • -contain โ€” match a substring of the header.
    • -end โ€” exact match at the end of the string (suffix match).
    • -exact โ€” exact string match.
    • -icase โ€” case-insensitive comparison.
    • -pcre or -perl โ€” use a Perl-compatible regular expression (requires compilation-time support โš ๏ธ).
    • -posix โ€” use a POSIX extended regular expression.
    • -re โ€” use regular expression matching, as set by the RegexType statement.

The default is -re, meaning pattern is treated as a regular expression as defined by the RegexType statement. periodt ๐Ÿ’€

  • SetHeader "header: value" โ€” adds a header to the request passed to the backend server. The argument undergoes string expansion as described above. The expanded value must be a valid header line โ€” no funny business ๐ŸŒธ
  • SetURL "value" โ€” sets the URL part of the request. The argument is subject to string expansion.
  • SetPath [-encode] "value" โ€” sets the path part of the request. The argument is subject to string expansion and, if -encode is given, percent-encoding.
  • SetQuery "value" โ€” sets the query part of the request. The argument is subject to string expansion.
  • SetQueryParam "name" [-encode] "value" โ€” modifies the query. Sets the query parameter name to value. The value argument is subject to string expansion and, if -encode is given, percent-encoding. ๐ŸŽ€
  • Rewrite [request|response] ... [ Else ... ] End โ€” conditionally modifies the request or the response depending on whether it matches certain conditions. If the argument is omitted, request is assumed.

Request modification is described in detail in section REQUEST MODIFICATION DIRECTIVES.

Response modification is covered by section RESPONSE MODIFICATION. ๐Ÿฆ‹


๐ŸŒˆ Compatibility directives ๐ŸŒˆ

The following directives are retained for compatibility with previous versions of pound. They will be removed in future releases โ€” so like, don't get attached, bestie ๐Ÿ˜ญ

  • HeaderAdd "header: value" โ€” same as SetHeader.
  • AddHeader "header: value" โ€” same as SetHeader.
  • HeaderRemove "pattern" โ€” same as DeleteHeader.
  • HeadRemove "pattern" โ€” same as DeleteHeader.

๐ŸŒธ HTTPS Listener ๐ŸŒธ

okay so an HTTPS listener defines an address and port that pound will listen on for HTTPS requests. The listener declaration begins with the keyword ListenHTTPS on a separate line. The keyword may be followed by a quoted string supplying listener name. This name is a unique label that identifies the listener.

All configuration directives enclosed between ListenHTTPS and End are specific to a single HTTPS listener. At the very least you must specify an address, optionally a port, and a server certificate for each listener. All directives defined for HTTP listeners are applicable to HTTPS listeners as well. The following additional directives are also available:

  • Cert "filename" โ€” specify the server certificate. Filename is either a certificate file name, or the name of a directory containing certificate files.

Certificate file is a file containing the certificate, possibly a certificate chain and the signature for this server, in that order.

This directive is mandatory for HTTPS listeners.

Please note that multiple Cert directives are allowed if your OpenSSL version supports SNI. In such cases, the first directive is the default certificate, with additional certificates used if the client requests them.

The ordering of the directives is important: the first certificate where the CN matches the client request will be used, so put your directives in the most-specific-to-least specific order (i.e. wildcard certificates after host-specific certificates).

Cert directives must precede all other SSL-specific directives.

  • ClientCert 0|1|2|3 depth โ€” ask for the client's HTTPS certificate: 0 โ€” don't ask (default), 1 โ€” ask, 2 โ€” ask and fail if no certificate was presented, 3 โ€” ask but do not verify. Depth is the depth of verification for a client certificate (up to 9). The default depth limit is 9, allowing for the peer certificate and additional 9 CA certificates that must be verified.
  • Disable SSLv2|SSLv3|TLSv1|TLSv1_1|TLSv1_2 โ€” disable the protocol and all lower protocols as well. This is due to a limitation in OpenSSL, which does not support disabling a single protocol. For example, Disable TLSv1 would disable SSLv2, SSLv3 and TLSv1, thus allowing only TLSv1_1 and TLSv1_2.
  • Ciphers [typeopt] "ciphers" ... โ€” defines the list of ciphers (for TLSv2 and below) or ciphersuites (for TLSv3) that will be accepted by the SSL connection. The typeopt option defines how to interpret the ciphers argument that follows it:
    • -cipherlist โ€” sets the list of ciphers that affects only TLSv2 and prior protocols. This is the default.
    • -ciphersuites โ€” sets the list of TLSv3 ciphersuites. It will be combined with any TLSv1.2 and below cipher lists that may have been configured.

Refer to ciphers(1), for a discussion of the ciphers argument.

  • SSLHonorCipherOrder bool โ€” if set to true, the server will broadcast a preference to use ciphers in the order supplied in the Ciphers directive. If the value is false, the server will accept any cipher from the Ciphers list. Default value is false.
  • SSLAllowClientRenegotiation 0|1|2 โ€” if this value is 0, client initiated renegotiation will be disabled. This will mitigate DoS exploits based on client renegotiation, regardless of the patch status of clients and servers related to "Secure renegotiation". If the value is 1, secure renegotiation is supported. If the value is 2, insecure renegotiation is supported, with unpatched clients. This can lead to a DoS and a Man in the Middle attack! The default value is 0.
  • CAlist "filename" โ€” set the list of "trusted" CA's for this server. The filename is the name of a file containing a sequence of CA certificates (PEM format). The names of the defined CA certificates will be sent to the client on connection.
  • VerifyList "filename" โ€” set the CA (Certificate Authority). The filename is a file that contains the CA root certificates (in PEM format).

Please note: there is an important difference between the CAlist and the VerifyList. The CAlist tells the client (browser) which client certificates it should send. The VerifyList defines which CAs are actually used for the verification of the returned certificate.

  • CRLlist "filename" โ€” set the CRL (Certificate Revocation List) file. The filename is a file that contains the CRLs (in PEM format).
  • NoHTTPS11 0|1|2 โ€” behave like an HTTP/1.0 server for HTTPS clients. If this value is 0, disable the check. If the value is 1, do not allow multiple requests on SSL connections. If the value is 2 (default), disable multiple requests on SSL connections only for clients whose User-Agent header contains substring MSIE. This may be required as a work-around for a bug in certain versions of IE.

๐Ÿฆ‹ Tunnel ๐Ÿฆ‹

Tunnel is a special listener that defines a transparent, protocol-agnostic communication channel between two sockets. Anything sent to one end of the tunnel appears at its other end and vice-versa.

Tunnels can be thought of as a software equivalent of port forwarding.

Syntactically a tunnel is declared using the following construct:

Tunnel [ "ID" ]
    Address IPADDR
    Port PORT
    Backend
        Address IPADDR
        Port PORT
    End
End

๐ŸŽ€ Service ๐ŸŽ€

A service is a definition of which backend servers pound will use to reply to incoming requests. A service may be defined as part of a listener (in which case it will be used only by that listener), or globally (which makes it available to all listeners). pound selects a listener based on user-supplied conditions that analyze the incoming request URL and/or headers. It will always scan listener-specific services first. If none matches, it will try the global ones. Services are tried in the same order as they are defined in configuration.

All configuration directives enclosed between Service and End are specific to a single service. They can be subdivided into two categories: service matching directives and backend definitions.

โœจ Service Matching Directives โœจ

These directives determine whether a particular request should be handled by this service. When a request arrives, each service is considered in turn (first services defined within the listener that received the request, then the ones defined in global scope). The first service that matches the request will be used. If no service matches, a 503 "Service unavailable" error is returned.

Unless explicitly stated in the configuration file, all matching directives are joined by a boolean AND.

A service with no matching directives always matches.

  • ACL [-forwarded] "name" โ€” match the source IP address against the named ACL. The ACL must have been defined earlier (see the ACL statement in GLOBAL DIRECTIVES section above). If the originating IP doesn't match any CIDR from the list, then this service will be skipped and next one tried.

The -forwarded keyword, if present, instructs the program to deduce originator IP from the X-Forwarded-For header, instead of using the actual source IP address. See the description of %a specifier in REQUEST LOGGING, for a detailed discussion of how the IP is determined.

  • ACL [-forwarded] โ€” this statement defines an unnamed ACL to match the source IP against. See above for the meaning of the optional argument. This line must be followed by one or more lines defining CIDRs, as described in the GLOBAL DIRECTIVES section above. The ACL definition is finished with a End keyword on a line by itself.

Semantically it is equivalent to the named ACL reference described above.

  • ACL [-forwarded] -file "filename" โ€” same as above, but reads ACL definition from file filename. The name is taken relative to the IncludeDir directory, unless it starts with a slash.

The ACL file should contain one CIDR per line. CIDRs need not be quoted. Empty lines and comments are allowed.

The -forwarded optional keyword has the same meaning as described above.

  • ACL [-forwarded] -filewatch "filename" โ€” same as above, but filename will be watched for changes during the runtime. Once a change is detected, the file will be reloaded and the ACL updated.

If possible, this uses OS-specific filesystem monitoring API (inotify(7), on GNU/Linux and kqueue(7) on *BSD systems). On systems that don't provide such API, the file will be checked for changes periodically, in intervals defined by the WatcherTTL setting.

  • BasicAuth [option] "filename" โ€” evaluates to true if the incoming request passes basic authorization as described in RFC 7617. filename is the name of a plain text file containing usernames and passwords, created with htpasswd(1) or similar utility. Unless it starts with a slash, it is taken relative to the IncludeDir directory. The file is read at startup and cached in memory, so that authorizations normally don't result in disk operations. pound will monitor the file using the same mechanism as for ACL -filewatch (see above), and reload it when it changes.

See the section entitled BASIC AUTHENTICATION, for a detailed discussion.

For consistency with other conditions, BasicAuth accepts an option: -file or -filewatch. The latter is the default and normally need not be specified explicitly. The former can be used to disable file change monitoring.

  • ClientCert "filename" โ€” evaluates to true if the client presented the certificate matching that from the file filename (PEM format).

This conditional cannot be used in standalone services (i.e. services that are defined in global scope), or if the ListenHTTPS section that hosts the service has the ClientCert statement on its own.

๐Ÿ’– the directives (part 2, electric boogaloo)


ContentCapture SIZE

okay so this one turns ON capturing of the incoming request content. if the content length is less than or equal to SIZE, the content gets captured and stashed away while it's being sent over to the backend. then that saved value is handed to your Lua procedures (the ones invoked by LuaMatch, LuaModify, and LuaBackend) through the variable http.req.body. ๐Ÿซถ

Eval "NAME"

evaluates the detached condition called NAME. and like โ€” the condition has to be defined by a Condition statement that comes before this one (peep above, section GLOBAL DIRECTIVES โœจ). bonus: if multiple Eval statements point at the same NAME, the condition gets evaluated exactly once and the result is reused by the later statements. efficiency icon behavior, no cap ๐Ÿ’…

Header [options] "pattern"

the request must contain at least one header matching the given pattern. by default, pattern is treated as a case-insensitive extended regular expression โ€” but you can switch that up with options, described below. ๐Ÿฆ‹

Header "Name" [options] "pattern"

alt form of the Header statement. in this form, pattern is matched against the value of each header that has the supplied Name. for example:

Header "Content-Type" -beg "text/"

Host [options] "hostname"

the request must contain a Host header whose value matches hostname. if you don't pass any options, case-insensitive exact match is assumed โ€” meaning this construct is literally equivalent to:

Header "Host" -exact "hostname"

see below for the whole discussion of options and how they affect matching ๐Ÿ‘€

this statement exists to make virtual host handling easier. check the EXAMPLES section.

Method [options] "pattern"

matches the request method. simple as that, periodt.

Path [options] "pattern"

matches the path part of the incoming request. ๐ŸŽ€

Query [options] "pattern"

matches the query part of the incoming request. important: the argument must be properly percent-encoded if it contains whitespace or other non-printable characters. don't skip that part bestie ๐Ÿ™ˆ

QueryParam "name" [options] "pattern"

matches the value of the query parameter name.

StringMatch "string" [options] "pattern"

expands string the way String expansion describes (see below โฌ‡๏ธ) and matches the resulting value against pattern.

TBF "key" rate burst

defines a token bucket filter for that service. there's a whole discussion below in the section entitled REQUEST RATE if you want the full lore. arguments are:

  • key โ€” a string identifying the bucket to use. it's subject to string expansion (see the subsection String expansion, above).
  • rate โ€” allowed request rate, requests per second. fractional rates are allowed too: 1/2 (one request each two seconds), 3/500ms (three requests each 500 milliseconds), etc. ๐Ÿ“
  • burst โ€” burst size, i.e. maximum number of tokens the bucket can contain.

the TBF conditional evaluates to true if the request conforms to the defined rate policy, and false otherwise. slay.

URL [options] "pattern"

matches the URL of the incoming request. by default, pattern is treated as a case-sensitive extended regular expression โ€” yes, different from Header, don't come for me ๐Ÿ˜ญ โ€” and this can be changed by the options, described below.


โœจ the options โœจ

the options argument in all those directives above is how you pick the comparison method. it's made up of zero or more option flags from this list:

  • -beg โ€” exact match at the beginning of string (prefix match).
  • -case โ€” case-sensitive comparison.
  • -contain โ€” match substring.
  • -end โ€” exact match at the end of string (suffix match).
  • -exact โ€” exact string match.
  • -file โ€” treat pattern as the name of a file to read patterns from. if the name is relative, it gets looked up in the include directory (see the discussion of the IncludeDir directory above). patterns are read from the file line by line. leading and trailing whitespace is removed. empty lines and comments (lines starting with #) are ignored. ๐Ÿงš
  • -filewatch โ€” same as -file, except the file is monitored for changes and reloaded as soon as they happen. if possible this uses an OS-specific API (inotify(7) on GNU/Linux and kqueue(7) on *BSD systems). on systems that don't provide such an API, the file gets checked for changes periodically. the interval between two successive checks is defined by the WatcherTTL setting.
  • -icase โ€” case-insensitive comparison.
  • -pcre or -perl โ€” use Perl-compatible regular expression (requires compile-time support). this overrides global RegexType settings. ๐Ÿ”ฅ
  • -posix โ€” use POSIX extended regular expression. this overrides global RegexType settings.
  • -re โ€” use regular expression matching, as set by the RegexType statement.
  • -tag "T" โ€” tag the statement with string T. that string can be used in subsequent modification statements to refer to a subexpression of this statement's pattern, using the syntax $I(T), where I is the ordinal number of the subexpression.

for example, the following matches any request whose Host header begins with "www." (case-insensitive):

Host -icase -beg "www."
๐Ÿ’ก also: Path and QueryParam directives accept the option -decode, which makes the obtained value undergo percent-decoding before the comparison. handy, iykyk.

๐Ÿ’€ Compatibility directives

these next ones are here purely for backward compatibility with older versions of pound. they will be removed in future versions โ€” so like, don't build your whole personality on them, bestie. ๐Ÿซถ

  • HeadRequire "pattern" โ€” same as Header.
  • HeadDeny "pattern" โ€” a shortcut for:
Not header "pattern"

in other words: the request may not contain any header matching the given pattern. see the Negation section, below.


๐Ÿ™ƒ Negation

prefix any of the directives discussed above with not and it reverts the sense of the comparison. easy. for example:

Not url "^/static/"

will match any request whose URL does not begin with "/static/".

negating compound statements is allowed too, e.g.:

Not ACL
   "192.0.2.0/26"
   "203.0.113.0/24"
End

โญ Match statement

a Match statement lets you group matching directives using arbitrary boolean operations. the syntax is:

Match OP
  directives...
End

where OP is AND or OR (case-insensitive ๐ŸŒˆ), and directives stands for any number of the matching directives discussed above (including the Match directive itself โ€” yes, it's recursive, she contains multitudes).

prefixing a Match directive with the word not reverts its result.

Match directives can be nested to any depth. literally any. go wild.

technically speaking, an implicit Match AND block is created around all the unenclosed matching directives on the top level of a Service block. so it was there the whole time ๐Ÿ‘‘


๐Ÿฆ‹ Modification directives

the following directives modify the incoming request before it gets passed to the selected backend. full deets in the section REQUEST MODIFICATION, below.

DeleteHeader [options] "pattern"

removes matching headers from the incoming request. for the list of available options and their effect on matching, see the description of the DeleteHeader statement in section HTTP Listener, subsection Modification directives, above. ๐Ÿ’…

SetHeader "header: value"

adds the defined header to the request passed to the backend server.

SetURL "value"

sets the URL part of the request.

SetPath [-encode] "value"

sets the path part. if you give it the -encode option, value gets percent-encoded after the usual expansions.

SetQuery "value"

sets the query part.

SetQueryParam "name" [-encode] "value"

modifies the query. sets the query parameter name to value. if you give it the -encode option, value gets percent-encoded after the usual expansions.

Rewrite [request|response] ... [ Else ... ] End

conditionally modifies request or response depending on whether it matches certain conditions. if the argument is omitted, request is assumed.

request modification is described in detail in section REQUEST MODIFICATION DIRECTIVES.

response modification is covered by section RESPONSE MODIFICATION.

RewriteErrors bool

if bool is true, this enables backend error response rewriting for this service โ€” i.e. replacing the content of error responses returned by a regular backend with a custom error text defined by the ErrorFile statement in the listener. see ERROR RESPONSE REWRITING for the full deep dive on this feature. ๐ŸŽ€

this statement overrides settings of the enclosing listener, so that:

RewriteErrors off

appearing inside a Service will disable that feature for that particular service. sneakyyyy ๐Ÿ™ˆ


๐Ÿ‘‘ Backend definitions

Balancer random | iwrr

defines the load-balancing strategy to use. possible arguments are: random, to use the weighted random balancing algorithm, and iwrr, meaning interleaved weighted round robin balancing. see REQUEST BALANCING, above, for the detailed discussion of these strategies.

this directive overrides the Balancer directive in global scope.

Backend

the directives enclosed between Backend and the following End directives define a single backend server (details below โฌ‡๏ธ). you can define multiple backends per service, in which case pound will distribute the load between them according to the selected balancer strategy. load balancing era ๐Ÿ’…

Control

enables a special control backend โ€” a management interface that returns info about the running pound server, makes it possible to change the state of configured listeners, services and backends, and provides other management facilities. the management interface is discussed in detail in section Control socket, above. the Control backend provides remote access to this interface.

โš ๏ธ be careful to properly protect the control interface using HTTPS, ACLs and/or basic authorization, e.g.:
ListenHTTPS
    Address 192.0.2.1
    Port 443
    Cert "/etc/ssl/priv/example.pem"

    Service
        Not BasicAuth "pound/htpasswd"
        Rewrite response
            SetHeader "WWW-Authenticate: Basic realm=\"Restricted access\""
        End
        Error 401
    End

    Service
        ACL "secure"
        Control
    End
End

like, fr fr, lock that thing down ๐Ÿ”’

๐ŸŒธ Emergency

The directives you tuck between Emergency and the next End define an emergency backend server (more deets below ๐Ÿ‘‡). You can define as many emergency servers as you want, no cap. A backend gets picked from the emergency pile only when all your regular backends are down. It's giving "break glass in case of emergency" ๐Ÿ’…

๐Ÿ’… Error STATUS [FILE]

A special-case backend that just returns an HTTP error page. The STATUS argument is the HTTP status code. The optional FILE argument is the name of a disk file holding the error page content โ€” if you pass it, the file gets handled exactly as in the ErrorFile directive (go look that one up, iykyk). If you don't pass it, the response content is figured out the usual way: first the ErrorFile STATUS statement for the enclosing listener gets consulted. If that's not there either, you get the default error page. ๐Ÿซถ

The Error directive is lowkey perfect for a catch-all service โ€” one that spits out an error page when no service matching the incoming request was found. Peep the EXAMPLES section for the details โœจ

๐Ÿ“Š Metrics

A special backend type that implements Openmetrics protocol output. There's a whole section called Metrics below if you wanna dive in ๐ŸŒŠ

๐Ÿ” Redirect [code] "url"

This is a special type of backend, bestie. Instead of sending the request off to a backend, pound replies immediately with a redirection to the given URL. Slay.

You're allowed to define multiple redirectors in a service, and even mix them with regular backends โ€” but honestly there's little if any justification for that, so it's deprecated ๐Ÿ™ˆ

The optional code can be one of: 301, 302 (the default), 303, 307, or 308.

The redirection destination is determined by the actual url you specify, and it's expanded as described in String expansion above.

For compatibility with previous pound versions, if no $N references are found in url, this logic kicks in: if it's a "pure" host (i.e. with no path) then the client gets redirected to the host you specified, with the original request path appended. If your url does contain a path, then the request path is ignored ๐Ÿ’€

Examples: the following swaps the first two path components of the original URL:

Service
    Host -re "(.+)"
    URL "^/([^/]+)/([^/]+)(/.*)?"
    Redirect "http://$0(1)/$2/$1$3"
End

Notice the use of Host to supply the hostname part for the redirect ๐Ÿ‘‘

Using request accessors, the above example can be rewritten as:

Service
    URL "^/([^/]+)/([^/]+)(/.*)?"
    Redirect "http://%[header host]/$2/$1$3"
End

Compatibility syntax: if you specified

Redirect "http://abc.example"

and the client requested http://xyz/a/b/c then it will be redirected to http://abc.example/a/b/c, but if you specified

Redirect "http://abc.example/index.html"

it will be sent to http://abc.example/index.html.

๐Ÿ—‚๏ธ SendFile DIR

The static file backend, and it's iconic tbh. It responds with the content of a file from directory DIR. The path part of the request supplies the path to the requested file, relative to the directory DIR. That path may be modified โ€” if need be โ€” using request rewriting techniques (in particular SetPath) discussed in REQUEST MODIFICATION DIRECTIVES.

Content type of the response is assumed to be text/plain. You can change that using the SetHeader statement (see RESPONSE MODIFICATION) ๐ŸŽ€

โœ… Success

A special backend that responds with code 200. Use it for debugging or for side effects, like setting headers with the SetHeader statement. Main character energy fr fr ๐Ÿ’–


๐Ÿงš Session control

๐ŸŒธ Session

Directives enclosed between Session and the following End directives define a session-tracking mechanism for the current service. See below for details ๐Ÿ‘€


๐Ÿฆ‹ Other directives

๐Ÿ’œ Constant "NAME" [OPTIONS] ["VALUE"]

Defines a configuration constant for use in string expansions and Lua code. See Constant in section GLOBAL DIRECTIVES for a detailed discussion of its arguments and their meaning.

Per-service constants augment per-listener and global ones. If the name of a per-service constant coincides with the name of a per-listener or globally defined one, the former overrides the latter for this service. Periodt โœจ

๐Ÿ™ˆ IgnoreCase bool

Override the global IgnoreCase setting.

โš ๏ธ Heads up bestie: this statement is deprecated and will be removed in future versions. Please use the -icase option to the URL directive instead. See the discussion of options in Service Matching Directives section above.

๐Ÿ’ค Disabled bool

Start pound with this service disabled (true) or enabled (false). If started as disabled, the service can be later enabled with poundctl(8). Cute little on/off switch, love that for you ๐ŸŽ€

๐Ÿ“จ ForwardedHeader name

Defines the name of the HTTP header that carries the list of proxies the request has passed through. It's used to report the originator IP address when logging โ€” see the description of the %a specifier in REQUEST LOGGING. This statement overrides the ForwardedHeader directive from the listener scope.

The default is X-Forwarded-For.

๐Ÿ”’ TrustedIP

Defines a list of trusted proxy IP addresses, used to determine the originator IP. See the description of the %a specifier in REQUEST LOGGING for a detailed discussion.

This statement overrides the TrustedIP directive from the listener scope.

๐Ÿคซ LogSuppress class

Suppresses HTTP logging for requests that resulted in status codes from the specified class. The valid status classes are:

  • info or 1 โ€” 1xx response codes.
  • success or 2 โ€” 2xx response codes.
  • redirect or 3 โ€” 3xx response codes.
  • clterr or 4 โ€” 4xx response codes.
  • srverr or 5 โ€” 5xx response codes.
  • all โ€” all response codes.

Multiple arguments are allowed ๐Ÿ‘ฏ

This statement is designed for services that receive a constant stream of similar HTTP requests from a controlled set of IP addresses, such as e.g. Openmetric services. See the Metric section below for an example ๐Ÿ‘€


๐ŸŒˆ ACME

This statement creates a service specially crafted for answering ACME HTTP-01 challenge requests (see https://letsencrypt.org/docs/challenge-types/#http-01-challenge). It takes a single argument specifying a directory where ACME challenges are stored. That argument is subject to string expansion (see the subsection String expansion, above) โœจ

It's supposed that another program is started periodically, which checks for certificates approaching their expiration, issues renewal requests and stores the obtained ACME challenges in that directory. Teamwork makes the dream work ๐Ÿซถ

๐ŸŽ€ Backend ๐ŸŽ€

okay so a backend is literally just the definition of one single backend server that pound uses to reply to your incoming requests. that's it. that's the whole vibe. backends get defined with a Backend statement, and there are two flavors: service-specific and global (aka "named"). we'll get into both later in this section, don't worry bestie. everything you put between Backend and End applies to that one backend only.

here's what you can put in there:

  • Address address โ€” the address pound connects to. this can be a numeric IP address, a symbolic host name (which has to be resolvable at run-time, no pressure or anything), or a full pathname of a UNIX socket. if the name can't be resolved to a valid address, pound will assume it's the path to a Unix-domain socket. this one is mandatory โ€” no Address, no backend, periodt.
  • Port port โ€” the port number or service name pound connects to. mandatory for non Unix-domain backends. (if it is a Unix socket, you get a pass ๐Ÿ’…)
  • HTTPS โ€” the backend is using HTTPS. that's it. that's the whole directive. slay.
  • ServerName "name" โ€” sets the name to use for server name identification (SNI). this directive also rewrites the Host: header for this particular backend, so you don't have to also use SetHeader on top of it. one less thing, love that for you. โš ๏ธ may only appear after the HTTPS directive.
  • Cert "filename" โ€” the certificate pound uses as a client. the filename is the file containing the certificate โ€” possibly a certificate chain and the signature too. also only allowed after HTTPS.
  • Disable SSLv2|SSLv3|TLSv1|TLSv1_1|TLSv1_2 โ€” disables the protocol and all lower protocols as well. why? because of a limitation in OpenSSL, which does not support disabling a single protocol. so like, Disable TLSv1 would disable SSLv2, SSLv3 and TLSv1, thus allowing only TLSv1_1 and TLSv1_2. ๐Ÿ’€ only after HTTPS, bestie.
  • Ciphers [typeopt] "ciphers" ... โ€” defines the list of ciphers (for TLSv2 and below) or ciphersuites (for TLSv3) to negotiate with the backend. the typeopt option defines how to interpret the ciphers argument that follows it:
    • -cipherlist โ€” sets the list of ciphers that affects only TLSv2 and prior protocols. this is the default.
    • -ciphersuites โ€” sets the list of TLSv3 ciphersuites. it gets combined with any TLSv1.2 and below cipher lists that may have been configured.

refer to ciphers(1) for a whole discussion of the ciphers argument. and yeah โ€” only after HTTPS. ๐Ÿซถ

  • Priority n โ€” the priority of this backend. higher priority backends get used more often than lower priority ones, so you should give higher priorities to your more capable servers. the scheduling algorithm depends on the load balancing strategy in use (peep the REQUEST BALANCING section above ๐Ÿ‘€). allowed range for priorities is 0 to 65535, inclusive. default value is 5.
  • TimeOut n โ€” override the global TimeOut value.
  • ConnTO n โ€” override the global ConnTO value.
  • WSTimeOut n โ€” override the global WSTimeOut value.
  • Disabled bool โ€” start pound with this backend disabled (1) or enabled (0). if it starts disabled, you can enable it later with poundctl(8). iconic.

โœจ Dynamic backends โœจ

okay this is the fun part โ€” dynamic backends get created and updated on the fly based on info from DNS. ๐Ÿฆ‹ a backend is declared dynamic by the Resolve statement, and its value defines what type of DNS record is used to generate the dynamic backend instances:

  • first โ€” resolve the symbolic host name from the Address directive and use the first IP from the DNS response as the address of the created dynamic backend. thus, at most one dynamic backend will be produced.
  • all โ€” resolve the symbolic host name from the Address directive and create one backend for each address from the DNS response. this enables load balancing between the created backends. each backend gets assigned the same priority. ๐ŸŒˆ
  • srv โ€” obtain SRV records for the host name and use them to generate backends. each record produces a new dynamic backend of the Resolve all type, which produces regular backends as described above. the weight field of the SRV record is mapped to the priority field of each generated backend. the SRV priority field identifies the balancer group where the backend will be hosted.

and these directives let you configure your dynamic backends even further:

  • Family any | inet | inet6 โ€” selects the family of addresses to look for:
    • any โ€” use all address families available. this is the default.
    • inet โ€” use only IPv4 addresses (A DNS RRs).
    • inet6 โ€” use only IPv6 addresses (AAAA DNS RRs).
  • IgnoreSRVWeight bool โ€” when using SRV records, ignore their weight fields and assign priorities for the generated backends from the priority of the producing backend. only valid when used together with Resolve srv. ๐Ÿ’…
  • OverrideTTL N โ€” query DNS each N seconds for possible changes in the configuration of the existing dynamic backends. if you don't use this statement, the TTL value returned with the DNS response gets used instead.
  • RetryInterval N โ€” retry failed DNS queries each N seconds. default is 600.

๐ŸŒธ Global and per-service backends ๐ŸŒธ

a Backend definition can appear as part of a service (i.e. inside a Service section) or globally. for the global one, the Backend keyword has to be followed by a symbolic name you assign to that backend, like:

Backend "name"
  ...
End

this name has to uniquely identify the backend among the other globally defined backends, no copy-paste name drama please. a global backend gets attached to a service using the UseBackend statement, which takes the symbolic name of the backend as its argument:

UseBackend "name"

this has essentially the same effect as if the full backend definition appeared in that spot instead. the substantial difference between service-specific and global backends is that a single global backend can be used in multiple services. in a way, UseBackend expands to a backend definition within the service. ๐Ÿซถ

and bestie, you can also adjust the Priority and Disabled parameters of the global backend definition for use in a particular service. the syntax goes like this:

Backend "name"
  Priority 3
  Disabled no
End

in this form, the Backend statement acts as UseBackend. only two directives can appear within such a Backend statement: Priority and Disabled. they override the settings defined in the global backend "name".

โš ๏ธ notice that โ€” in spite of their similarity โ€” the two named forms of the Backend statement serve different purposes: in global scope, a named Backend defines a backend for further use in various services, and within a service, it creates a copy of the backend defined elsewhere and attaches it to this service. same look, different job. iykyk.

the actual global definition may appear before as well as after the service or services it's used in. no strict order, we're chill like that. ๐Ÿ˜Œ


๐Ÿ’œ Resolver ๐Ÿ’œ

the Resolver section controls DNS lookups for dynamic backend generation (see above, bestie, keep up ๐Ÿ’–). it can contain these directives:

  • CNAMEChain N โ€” maximum allowed length of a CNAME chain. CNAME chains are formed by DNS CNAME records pointing to another CNAME. although it's prohibited by the RFC, this kind of usage does occur sometimes (rude). by default, pound does not accept CNAME chains. if you work with nameservers that return them, set this statement to a small integer value defining the max number of CNAMEs in the chain that can be accepted. a value of 2 or 3 should suffice in most cases.
  • ConfigFile "FILE" โ€” read the resolver configuration from FILE, instead of the default /etc/resolv.conf.
  • ConfigText ... End โ€” alternative way to supply resolver configuration. the material within this section is read verbatim and used as the content of the resolver configuration file. if both ConfigFile and ConfigText are used, the last used statement wins.
  • debug bool โ€” whether to enable DNS debugging info.
  • RetryInterval N โ€” interval in seconds, after which to retry failed DNS queries or queries that returned no RRs. this value is used unless the backend defines its own retry interval value.

๐Ÿ”ฅ Emergency ๐Ÿ”ฅ

emergency servers get used once all existing backends are "dead". ๐Ÿ’€ everything between Emergency and End is specific to a single service. for the list of directives allowed in Emergency sections, refer to the description of Backend โ€” same rules apply, bestie.


๐Ÿ“ Session ๐Ÿ“

this one defines how a service deals with possible HTTP sessions. all configuration directives between Session and End are specific to a single service. once a session is identified, pound will try to send all requests within that session to the same backend server. consistency is the moment. โœจ

here's what's available:

  • Type IP|BASIC|URL|PARM|COOKIE|HEADER โ€” what kind of sessions are we even looking for? IP (the client address), BASIC (basic authentication), URL (a request parameter), PARM (a URI parameter), COOKIE (a certain cookie), or HEADER (a certain request header). this is a mandatory parameter, no skipping.
  • TTL n โ€” how long can a session be idle (in seconds)? a session that's been idle for longer than the specified number of seconds gets discarded. also mandatory. ๐Ÿ•
  • ID "name" โ€” the session identifier. this directive is permitted only for sessions of type URL (the name of the request parameter we need to track), COOKIE (the name of the cookie), and HEADER (the header name).

see below for some examples. ๐Ÿ‘€


๐Ÿ‘‘ Metrics ๐Ÿ‘‘

the following service definition enables Openmetric telemetry output on endpoint /metrics:

Service
    URL "/metrics"
    Metrics
End

to control access to the telemetry endpoint, use the ACL statement.

the LogSuppress directive can be used in openmetric services to suppress logging of served HTTP requests.

for example:

Service
    URL "/metrics"
    Metrics
    ACL "secure"
    LogSuppress success
End

the metrics output is sufficiently self-documented by # HELP descriptor lines. so like, it explains itself, which is honestly main character energy. ๐ŸŒŸ

HIGH-AVAILABILITY ๐Ÿ’–

okay so pound is lowkey keeping tabs on your active backend servers at all times. It attempts to keep track of active backend servers, and will temporarily disable servers that do not respond โ€” and bestie, โ€œdo not respondโ€ does not necessarily mean dead: an overloaded server that pound cannot establish a connection to will be considered dead. ๐Ÿ˜ญ

But every Alive seconds, pound makes an attempt to connect to the dead servers in case they have become active again. If this attempt succeeds, connections will be initiated to them again. ๐Ÿซถ

In general it is a good idea to set this time interval as low as is consistent with your resources in order to benefit from resurrected servers at the earliest possible time. The default value of 30 seconds is probably a good choice. โœจ

The clients that happen to hit a dead backend server will just receive a 503 Service Unavailable message. ๐Ÿ’€


REQUEST MODIFICATION DIRECTIVES ๐ŸŽ€

Several statements are provided to modify incoming requests prior to passing them to the backend. These statements can be present both in ListenHTTP (ListenHTTPS) and in Service sections. ๐Ÿ’…

Basic request modification directives are:

  • SetURL "value" โ€” Set the URL of the incoming request to value.
  • SetPath [-encode] "value" โ€” Set the path part of the URL to the given string. The -encode option enables percent-encoding of value.
  • SetQuery "value" โ€” Set the query part of the URL to the given string. value must be a valid query with the special characters properly encoded using percent encoding.
  • SetQueryParam "name" [-encode] "value" โ€” Set the query parameter name to the value. If -encode is given, after usual expansions value will undergo percent-encoding.
  • SetHeader "name: value" โ€” Sets the HTTP header. If the header name already exists, it will be overwritten. Otherwise, new header will be added to the end of the header list.
  • DeleteHeader [options] "pattern" โ€” Remove from the request all headers matching pattern. By default, pattern is treated as extended POSIX regular expression. The options argument can be used to change this behavior. It consists of zero or more option flags from the following list:
    • -beg โ€” Exact match at the beginning of string (prefix match).
    • -case โ€” Case-sensitive comparison.
    • -contain โ€” Match substring.
    • -end โ€” Exact match at the end of string (suffix match).
    • -exact โ€” Use exact string matching.
    • -icase โ€” Case-insensitive comparison.
    • -pcre or -perl โ€” Use Perl-compatible regular expression (requires compilation-time support). This overrides global RegexType settings.
    • -posix โ€” Use POSIX extended regular expression. This overrides global RegexType settings.
    • -re โ€” Use regular expression matching, as set by the RegexType statement.

The value argument in the above directives is subject to string expansion. Refer to subsection String expansions, for details. ๐Ÿฆ‹

The Rewrite block statement is used to associate one or more of the above directives with request matching directives (discussed in the subsection Service Matching Directives above), so that request modification takes place only when the request matches certain conditions. ๐Ÿ‘‘

Syntactically, a Rewrite block is:

Rewrite [request]
.
.
.
Else
.
.
.
End

where dots stand for any number of request matching and request modification statements. The Else part is optional; any number of Else blocks can be supplied, thus providing for conditional branching. The following concocted example illustrates it:

Rewrite
   Path "\.(jpg|gif)$"
   SetPath "/images%[path]"
Else
   Match AND
     Host "example.org"
     Path "\.[^.]+$"
   End
   SetPath "/static%[path]"
Else
   Path "\.[^.]+$"
   SetPath "/assets%[path]"
End

Here, if a path ends with a filename with suffix ".jpg" or ".gif", "/images" is prepended to it (notice that path always starts with a slash, hence it should not be used explicitly). If the path ends with any other extension, the action depends on the host being addressed. The path is prefixed with "/static" if the host is "example.org", and with "/assets" otherwise. ๐ŸŒธ

To illustrate the effects of string expansion, hereโ€™s the above example rewritten using backreference expansion instead of request accessors:

Rewrite
   Path ".*\.(jpg|gif)$"
   SetPath "/images$0"
Else
   Match AND
     Host "example.org"
     Path ".*\.[^.]+$"
   End
   SetPath "/static$0"
Else
   Path ".*\.[^.]+$"
   SetPath "/assets$0"
End

Request modification directives from ListenHTTP (or ListenHTTPS) section are applied first, followed by directives from the Service section. Directives appearing within a single section are applied in the same order as they appear in the configuration file. โœจ


BUILT-IN HEADERS ๐ŸŽ€

In addition to the SetHeader and DeleteHeader directives discussed above, the HeaderOption directive controls insertion of the built-in headers:

HeaderOption opt...

There are two kinds of such headers: forwarded headers that convey information about original destination of the request, and ssl headers (for HTTPS connections), that hold information about server and client SSL certificates. These are discussed in detail below. ๐Ÿ’–

By default, both kinds of built-in additional headers are enabled. This default can be changed by using the HeaderOption directive. Placed in global scope, this directive sets global options. Used within a ListenHTTP or ListenHTTPS block, it affects only settings for that listener. ๐Ÿซถ

The opt values passed to the directive are:

  • all โ€” Enable both kinds of additional headers.
  • none โ€” Disable both kinds of additional headers.
  • forwarded โ€” Enable forwarded headers.
  • no-forwarded โ€” Disable forwarded headers.
  • ssl โ€” Enable ssl headers.
  • no-ssl โ€” Disable ssl headers.

The built-in headers are added before request modification directives are applied. Thus, you can use DeleteHeader and SetHeader to trim down headers added by HeaderOptions. ๐Ÿ’…

Forwarded Headers ๐ŸŒˆ

The headers in the forwarded header group are:

  • X-Forwarded-For โ€” IP address of the HTTP client that sent the request.
  • X-Forwarded-Proto โ€” Protocol (http or https) that the client used to connect to pound.
  • X-Forwarded-Port โ€” Destination port that the client used to connect to pound.

HTTPS Headers ๐Ÿ”ฅ

If a client browser connects to pound via HTTPS and the ssl header group is enabled, then the following header is added:

  • X-SSL-Cipher โ€” Contains SSL version followed by a slash and active cipher algorithm.

Additionally, if the client presented its certificate, the following headers are added that describe the client certificate:

  • X-SSL-Subject โ€” Details about the certificate owner.
  • X-SSL-Issuer โ€” Details about the certificate issuer (Certificate Authority).
  • X-SSL-NotBefore โ€” Starting date of certificate validity.
  • X-SSL-NotAfter โ€” Ending date of certificate validity.
  • X-SSL-Serial โ€” Certificate serial number (decimal).
  • X-SSL-Certificate โ€” The full client certificate (PEM-format multi-line).

RESPONSE MODIFICATION ๐Ÿ’œ

Headers in the response obtained from a regular backend or generated with an Error backend can be modified before sending them back to the requesting server. A special form of the Rewrite directive is provided for this purpose:

Rewrite response
.
.
.
Else
.
.
.
End

where dots stand for any number of response matching and modification statements. The Else part is optional; any number of Else blocks can be supplied, thus providing for conditional branching. ๐Ÿฆ‹

This directive can be used in ListenHTTP, ListenHTTPS, and Service definitions. ๐Ÿ’–

Response modification directives from the Service section are applied first, followed by directives from the ListenHTTP (or ListenHTTPS) section. Directives appearing within a single section are applied in the same order as they appear in the configuration file. โœจ

Response matching statements are:

  • Header [options] "pattern" โ€” Matches if the response contains at least one header matching the given pattern.
  • Header "name" [options] "pattern" โ€” Alternative form of the Header statement, that matches pattern against value part of each header name.
  • StringMatch "string" [options] "pattern" โ€” Expand string as described in String expansion (see below) and match the resulting value against pattern. By default, pattern is treated as case-insensitive extended regular expression. This can be changed by options, as described in section Service Matching Directives, above.
  • Match โ€” The Match statement is as described in section Match statement, except that only directives described above are allowed within it.
  • Not โ€” Placed before any of the above directives, reverts its result.

Following response modification statements are defined:

  • DeleteHeader "pattern" โ€” Remove matching headers from the response.
  • SetHeader "header: value" โ€” Add header to the response. Argument undergoes string expansion as described above. The expanded value must be a valid header line.

ERROR RESPONSE REWRITING ๐Ÿ‘‘

When a regular backend responds with an error, the content of that response can be replaced with an arbitrary custom page, if its status code is one of the http status codes supported by pound, i.e. one of 400, 401, 404, 405, 413, 414, 500, 501, 503, and a custom error page for that status is provided using the ErrorFile directive (or one of the legacy ErrNNN directives). ๐Ÿซถ

To enable such error response rewriting use the following statement:

RewriteErrors on

in the Service block, where the responding backend is defined, or in the enclosing ListenHTTP (ListenHTTPS) block. ๐Ÿ’…

๐ŸŽ€ MULTI-VALUE HEADERS ๐ŸŽ€

Okay bestie, so HTTP is lowkey dramatic and lets certain headers show up multiple times in one message. Specifically, multiple headers with the same header name are allowed if that header field is defined as a comma-separated list. The standard says those fields can be combined into a single header: value pair by appending each subsequent field value to the previous one, each separated by a comma. Slay.

Pound can do that combining on incoming requests and on responses. To turn it on, declare the names of headers that can be combined using the CombineHeaders section statement. This statement must be followed by header names, each on a separate line, and terminated with an End statement. E.g.:

CombineHeaders
    "Accept"
    "Allow"
    "Forwarded"
End

You can also store header names in a file and include that file after CombineHeaders, e.g.:

CombineHeaders
    Include "multivalue.txt"
End

๐Ÿ” BASIC AUTHENTICATION ๐Ÿ”

To limit access to a service or services to certain authenticated users, use a combination of BasicAuth matching and response modification. It's giving security gatekeeper energy.

The BasicAuth matching directive evaluates to true if the incoming request contains an Authorization header with scheme "Basic", such that the user name and password obtained from it match a line in the given disk file. The file must be a plain-text file created with htpasswd(1) or a similar utility, i.e. each non-empty line of it must contain a username and password hash separated by a colon. The password hash can be one of:

  1. Password in plain text.
  2. Hash created by the system crypt(3) function.
  3. Password hashed using SHA1 algorithm and encoded in base64. This hash must be prefixed by {SHA}.
  4. Apache-style apr1 hash.

Combined with the response rewriting described above, this can be used to implement basic HTTP authentication in Pound as shown in the following example:

Service "auth"
    Not BasicAuth "/etc/pound/htpass"
    Rewrite response
        SetHeader "WWW-Authenticate: Basic realm=\"Restricted access\""
    End
    Error 401
End

Placing this service definition before others ensures that services defined below it will be tried only if the incoming request passes basic authentication. Iconic ordering behavior, honestly.


๐Ÿšฆ REQUEST RATE ๐Ÿšฆ

Request rate can be controlled using the TBF service selection statement. The statement implements a token bucket filter and takes three arguments: a string identifying the bucket, request flow rate in requests per second, and token burst size, i.e. maximum number of requests per bucket. The first argument is expanded as discussed in String expansion. The statement evaluates to true if the request conforms to the rate policy, and to false otherwise. It is customary to use its negation together with the Error special backend in order to control request flow, e.g.:

Service "rate-limit"
  Path -re "^/([^/]+).*"
  Not TBF "$1" 3 10
  Error 429
End

๐Ÿ“ REQUEST LOGGING ๐Ÿ“

Logging of HTTP requests and responses is controlled by the LogLevel directive. This directive can appear either in global scope, in which case it affects all listeners defined below it, or in listener scope, in which case it affects only this listener, overriding the global setting. Very main-character-of-the-listener behavior.

Argument to the LogLevel directive can be either an integer number in range 0 through 5 (inclusive), or a quoted string. Numeric value identifies one of the built-in log formats, described later in this section. String value refers either to a built-in format name, or to a user-defined format name. In the latter case, the named format must be defined earlier using the LogFormat statement.

โœจ Format specification โœจ

The syntax of the LogFormat statement is:

LogFormat "name" "format_string"

First argument, name, is a unique identifier for this format, which will be used later to refer to it. Format_string is a character string composed of ordinary characters (not %), which are copied unchanged to the resulting log message, and conversion specifications, each of which are replaced by a corresponding piece of information about the request or reply.

Conversion specifications are single characters prefixed with a percent sign. Depending on the specification, an optional conversion argument in curly brackets may appear between % and the conversion character.

The following conversion characters are defined:

  • %% โ€” Replaced with the percent sign.
  • %a โ€” Originator IP address of the request. If the request contains the X-Forwarded-For header and TrustedIP ACL is defined, the value of the header is consulted to obtain the IP address. The value must be a comma-delimited list of intermediate user agent IP addresses. To determine the actual user agent IP, the list is traversed from right to left, until an IP is found that is not listed in TrustedIP ACL.

If X-Forwarded-For header is not present, or TrustedIP is not defined, or the above algorithm does not return an IP address, %a expands to the actual remote IP address the request came from (same as %h).

The TrustedIP ACL can be defined in global scope, or in ListenHTTP (ListenHTTPS) section, or in Service section. Most-specific ACL overrides least-specific ones, that is TrustedIP defined in Service will be used, if it is defined. If not, the one defined in listener will be used, etc. The syntax of the TrustedIP statement is the same as that of ACL, i.e.

TrustedIP "name"

refers to the named ACL name (which must be defined earlier),

TrustedIP -file "name"

or

TrustedIP -filewatch "name"

read the list of IPs (or CIDRs) from the file name, and

TrustedIP
  "CIDR0"
  "CIDR1"
  ...
End

defines the list of trusted IPs in place.

ForwardedHeader statement defines the name of the header to use instead of X-Forwarded-For. As TrustedIP, this statement can appear in global, listener, or in service scope.

  • %A โ€” Local IP address of the listener.
  • %B โ€” Size of response in bytes, excluding headers.
  • %b โ€” Same as %B, but in CLF format, i.e. a dash is used when response size is zero.
  • %D โ€” The time taken to serve the request, in microseconds.
  • %h โ€” Client IP address of the request.
  • %H โ€” The request protocol.
  • %{VARNAME}i โ€” The contents of VARNAME: header line in the request. Changes made by header modification directives affect this.
  • %{VARNAME}I โ€” Same as %i, except that if no such header is present in the request, a dash is substituted.
  • %l โ€” Single dash. This specifier is reserved for future use.
  • %{OBJNAME}L โ€” Location of the Pound object that is involved in handling the request. Valid values for OBJNAME are: listener, service, and backend. The location gives position in the configuration file where the object was defined, and is formatted as
NAME:LN1.COL1-LN2.COL2

where NAME is the configuration file name, LN1 and COL1 are number of line and column where the object definition begins, and LN2 and COL2 are number of line and column where it ends. Line and column numbers start with 1.

  • %m โ€” The request method.
  • %{OBJNAME}N โ€” Name of the Pound object that is involved in handling the request. Valid values for OBJNAME are: listener, service, and backend.
  • %p โ€” Port number of the listener serving the request.
  • %P โ€” Thread ID of the serving thread.
  • %q โ€” The query string prepended with a ? if it exists, otherwise an empty string.
  • %r โ€” First line of request.
  • %s โ€” Response status code.
  • %>s โ€” First line of the response.
  • %t โ€” Time the request was received, in the format [18/Sep/2011:19:18:28 -0400]. The last number indicates the timezone offset from UTC.
  • %{format}t โ€” Time the request was received, in the format specified by the argument (strftime(3)). If the format starts with begin: (default) the time is taken at the beginning of the request processing. If it starts with end: it is the time after the response from the backend has been sent back to the requester. In addition to strftime(3) formats, the following specifications are recognized:
    • sec โ€” Number of seconds since the Epoch.
    • msec โ€” Number of milliseconds since the Epoch.
    • usec โ€” Number of microseconds since the Epoch.
    • msec_frac โ€” Millisecond fraction of the time.
    • usec_frac โ€” Microsecond fraction of the time.
  • %T โ€” The time taken to process the request, in seconds.
  • %{UNIT}T โ€” The time taken to process the request, in a time unit given by UNIT. Valid units are ms for milliseconds, us for microseconds, s for seconds, and f for seconds with fractional part. Using s gives the same result as %T without any format; using us gives the same result as %D.
  • %u โ€” Remote user if the request was authenticated.
  • %U โ€” The URL path requested. This is affected by request modification directives.
  • %v โ€” Hostname used in the request (from the Host header, without port number).

๐ŸŒˆ Built-in formats ๐ŸŒˆ

There are five built-in formats. These can be identified either by their numeric index in the format table, or by symbolic name. The table below describes each built-in format using the format specification string:

  • 0, null โ€” No request logging at all.
  • 1, regular โ€”
"%a %r - %>s"
  • 2, extended โ€”
"%a %r - %>s (%{Host}i/%{service}N -> %{backend}N) %{f}T sec"
  • 3, vhost_combined โ€” (Split in two lines for readability)
"%v:%p %a %l %u %t \"%r\" %s %b \"%{Referer}i\" \
\"%{User-Agent}i\""
  • 4, combined โ€”
"%a %l %u %t \"%r\" %s %b \"%{Referer}i\" \"%{User-Agent}i\""
  • 5, detailed โ€” (Split in two lines for readability)
"%v:%p %a %l %u %t \"%r\" %s %b \"%{Referer}i\" \
\"%{User-Agent}\" (%{service}N -> %{backend}N) %{f}T sec"

๐Ÿ›ก๏ธ SECURITY ๐Ÿ›ก๏ธ

okay so bestie, pound is super low-key about your hard disk. The config file, server certificate files and error messages (ErrorFile) are opened read-only on startup, read, and then closed immediately afterwards. No lingering, no weird disk drama.

Read access during runtime is only required for certain special features, like BasicAuth, SendFile, and request matching directives with the -filewatch option. And the only disk write pound ever does is saving its PID in the pid file at startup. That's it. That's the whole disk footprint. ๐Ÿ’…

Giving such limited I/O, using a RootJail directive can provide only limited security bonus. Cute, but not a magic shield, iykyk.

pound tries to sanitize all HTTP/HTTPS requests: the request itself, the headers and the contents are checked for conformance to the RFCs, and only valid requests are passed to the backend servers. This is not absolutely fool-proof โ€” as the recent Apache problem with chunked transfers demonstrated. However, given the current standards, this is the best that can be done โ€” HTTP is an inherently weak protocol. fr fr. ๐Ÿ˜ญ


๐Ÿ’€ DEPRECATED FEATURES ๐Ÿ’€

Configuration statements

The following configuration statements are retained for backward compatibility with earlier pound versions. They will disappear from future releases โ€” so don't get too attached, bestie:

  • HeadRequire โ€” use Header instead. See Service Matching Directives, for details.
  • HeadDeny โ€” use Not Header. For details, see the Negation subsection in Service Matching Directives.
  • AddHeader and HeaderAdd โ€” use SetHeader instead. See the section REQUEST MODIFICATION DIRECTIVES, for details.
  • HeaderRemove and HeadRemove โ€” use DeleteHeader instead. See the section REQUEST MODIFICATION DIRECTIVES, for details.
  • IgnoreCase โ€” use the -icase option to the URL directive instead. See the discussion of options in the Service Matching Directives section.
  • ErrNNN (where NNN is one of: 400, 401, 403, 404, 413, 414, 500, 501, 503) โ€” use the ErrorFile directive, described in the section HTTP Listener, above.

Redirect request hack

The use of the redirect request hack in the Redirect statement is deprecated as well. Instead of relying on the trailing slash to append the original URL to the redirect location, use URL backreferences. For example, instead of

Redirect "http://example.org/"

use:

URL ".*"
Redirect "http://example.org$0"

Multiple redirect backends

Use of multiple Redirect backends in a single service is deprecated, as well as using Redirect backends together with regular ones. If you know of any valid usage for such configuration, please drop a note to the authors (see REPORTING BUGS, below). ๐Ÿซถ


๐Ÿ“ ADDITIONAL NOTES ๐Ÿ“

pound uses the system log for messages (default facility LOG_DAEMON). If using rsyslog, you can use the following configuration fragment to redirect pound messages to a separate file:

:programname, startswith, "pound" {
  /var/log/pound.log
  stop
}

The format requested by LogLevel 3 is understood by most log analyzers. iconic.

Translating HTTPS to HTTP is an iffy proposition: no client information is passed to the server itself (certificates, etc) and the backend server may be misled if it uses absolute URLs. pound tries to handle this by adding various X-Forwarded-* and X-SSL-* headers. See HEADER MODIFICATION above, for a detailed discussion.

pound deals with (and sanitizes) HTTP/1.1 requests. Thus even if you have an HTTP/1.0 server, a single connection to an HTTP/1.1 client is kept, while the connection to the backend server is re-opened as necessary. main character energy: keeps the client connection alive, reopens the backend like it's no big deal. โœจ

pound attempts to resolve the names of the hosts that appear in various requests and/or responses. That means it needs a functioning resolver of some kind (be it /etc/hosts, DNS or something else). However, the use of DNS can be disabled using the -W no-dns command line option.


๐ŸŒˆ EXAMPLES ๐ŸŒˆ

To translate HTTPS requests to a local HTTP server (assuming your network address is 192.0.2.1):

ListenHTTPS
    Address 192.0.2.1
    Port    443
    Cert    "/etc/pound/server.pem"
    Service
        Backend
            Address 127.0.0.1
            Port    80
        End
    End
End

To distribute the HTTP/HTTPS requests to three Web servers, where the third one is a newer and faster machine:

ListenHTTP
    Address 192.0.2.1
    Port 80
End

ListenHTTPS
    Address 192.0.2.1
    Port 443
    Cert    "/etc/pound/server.pem"
End

Service
    Backend
        Address 192.168.0.10
        Port 80
    End

    Backend
        Address 192.168.0.11
        Port 80
    End

    Backend
        Address 192.168.0.12
        Port 80
        Priority 3
    End
End

To separate between image requests and other Web content and send all requests for a specific URL to a secure server:

ListenHTTP
    Address 192.0.2.1
    Port 80
End

# Images server(s)
Service
    URL ".*.(jpg|gif)"
    Backend
        Address 192.168.0.12
        Port 80
    End
End

# redirect all requests for /forbidden

Service
    Url         -beg "/forbidden"
    Redirect    "https://xyzzy.com"
End

# Catch-all server(s)
Service
    Backend
        Address 192.168.0.10
        Port 80
    End

    Backend
        Address 192.168.0.11
        Port 80
    End

    Session
        Type    BASIC
        TTL     300
    End
End

Here is a more complex example: assume your static images (GIF/JPEG) are to be served from a single backend 192.168.0.10. In addition, 192.168.0.11 is to do the hosting for www.myserver.com with URL-based sessions. Two backends, 192.168.0.20 and 192.168.0.21, will handle the rest of requests (cookie-based sessions). Among these, the latter (192.168.0.21) is less powerful than the former, so care should be taken that it gets less requests. The logging will be done by backend servers. All plain HTTP requests are to be redirected to HTTPS and all requests directed to myserver.com โ€” to www.myserver.com. The configuration file may look like this:

User        "nobody"
Group       "nogroup"
RootJail    "/var/pound/jail"
Alive       60
LogLevel    0

# HTTP: redirect all requests to the corresponding HTTPS server.
ListenHTTP
    Address 192.0.2.1
    Port 80
    Client  10
    Service
        Host -re ".+"
        URL ".*"
        Redirect 301 "https://$0(1)$0"
    End
End

ListenHTTPS
    Address 192.0.2.1
    Port 443
    Cert    "/etc/pound/pound.pem"
    Client  20
End

# Image server
Service
    URL ".*.(jpg|gif)"
    Backend
        Address 192.168.0.10
        Port 80
    End
End

# Virtual host www.myserver.com
Service
    URL         ".*sessid=.*"
    Host        "www.myserver.com"
    Backend
        Address 192.168.0.11
        Port 80
    End

    Session
        Type    URL
        ID      "sessid"
        TTL     120
    End
End

# Virtual host myserver.com - redirects to www.
Service
    Host        "myserver.com"
    URL         ".*"
    Redirect 301 "https://www.myserver.com$0"
End

# Virtual host server1.com and www.server1.com
Service
    Host        -re "^(www.)?server1.com$"
    Backend
        Address 192.168.0.20
        Port 80
        Priority 5
    End

    Backend
        Address 192.168.0.21
        Port 80
        Priority 4
    End

    Session
        Type    COOKIE
        ID      "userid"
        TTL     180
    End
End

# Return custom error page if no matching service was found.
Service
    Error 404 "/var/lib/pound/notfound.html"
End

๐Ÿ“ FILES ๐Ÿ“

๐Ÿ’พ /var/run/pound.pid

This is where pound will attempt to record its process id. The exact location is determined at compile time by the value of the --localstatedir configuration switch. It can be changed at runtime using the -p command line option. Use pound -V to inspect the actual default.

๐Ÿ’พ /etc/pound.cfg

Default configuration file. The exact location is determined at compile time by the value of the --sysconfdir configuration switch. It can be changed at runtime using the -f command line option. Use pound -V to inspect the actual default.

๐Ÿ’พ /usr/local/etc/pound/cert.pem

Certificate file(s) for HTTPS. The location must be defined in the configuration file โ€” this is only a suggestion. The file must contain a PEM-encoded certificate, optionally a certificate chain from a known Certificate Authority to your server certificate and a PEM-encoded private key (not password protected). See openssl(1) for details. This file should be well protected, lest someone gets your server private key. ๐Ÿ”


๐Ÿ‘‘ AUTHOR ๐Ÿ‘‘

Written by Robert Segall (Apsis GmbH), and Sergey Poznyakoff.


๐Ÿ› REPORTING BUGS ๐Ÿ›

Report bugs to gray@gnu.org. You may also use github issue tracker at https://github.com/graygnuorg/pound/issues.


  • Copyright ยฉ 2002-2010 Apsis GmbH.
  • Copyright ยฉ 2018โ€“2026 Sergey Poznyakoff

License GPLv3+: GNU GPL version 3 or later (http://gnu.org/licenses/gpl.html)

This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law.


okay bestie, that's the whole POUND(8) page โ€” source/version pound, dated May 7, 2026. stay iconic out there ๐Ÿ’…โœจ๐Ÿฆ‹

๐Ÿ“ฆ where this page comes from