Configuration

Busker is configured with an ordinary Clojure map.

The same config shape is used when starting a server with ol.busker/start! and when changing a running server with ol.busker/reload!. You can write that map directly or generate it from environment variables, files, a database, or other application state. It’s just data.

When Busker starts or reloads, it turns your config map into a normalized config snapshot. A snapshot is the effective config after defaults are applied.

A running server is backed by a runtime generation. A generation is the set of runtime resources built from one snapshot. For example, it includes open sockets, worker threads, native protocol state, handlers, and TLS automation state.

On reload, Busker builds a new generation while the current one keeps serving traffic. If the new generation starts successfully, new connections and requests move to it. The old generation stops accepting new work and drains the connections and requests it is already handling. If the new generation fails to start, the old generation keeps serving traffic.

A minimal HTTP config with HTTP/1.1, HTTP/2, and automatic gzip/zstd/brotli compression is:

{:entrypoints {:http {:bind "127.0.0.1:8080"
                      :tls false}}
 :dispatch    [{:handler (fn [_req]
                           {:status 200
                            :headers {"content-type" "text/plain"}
                            :body "ok"})}]}

That’s how you would use Busker when coming from traditional Clojure ring-adapters like Jetty or http-kit.

A minimal HTTP+HTTPS config with HTTP/1.1, HTTP/2, HTTP/3, automatic gzip/zstd/brotli compression, and certificates obtained and renewed automatically is:

{:tls         {:certificates {:manage ["example.com"]}}
 :entrypoints {:http {:bind ":80"
                      :tls  false}
               :https {:bind ":443"}}
 :dispatch    [{:handler (fn [_req]
                           {:status 200
                            :headers {"content-type" "text/plain"}
                            :body "ok"})}]}

Note the addition of the example.com domain, that is required so Busker can manage the certificate lifecyle for you.

An entrypoint names one or more network listeners and the protocols they accept.

A dispatcher is one ordered step in the request pipeline that can match, transform, or handle requests for one or more entrypoints. In conventional Clojure programs there is usually only a single dispatcher: the Ring handler.

Busker accepts the following top-level config keys.

:entrypoints and :dispatch are required. Other keys are optional and either use their documented default or remain unset.

key value

:entrypoints

Map of entrypoint ids to entrypoint maps (see below).

:dispatch

Ordered vector of dispatcher maps (see below).

:tls

Global TLS, certificate, ACME, and session ticket config (see below).

:n-workers

Positive integer event loop worker count, default 1.

:buffer-pool

Response buffering pool implementing BufferPool.

:max-connections

Positive integer connection cap, default 1024.

:output-buffer-size

Response aggregation bytes, default 32768; 0 disables buffering.

:server-name

Server header value, default "ol.busker/dev".

:proxy-status-identity

Optional Proxy-Status identity.

:max-request-entity-size

Optional positive integer request body byte limit.

:max-delegations

Optional positive integer internal delegation limit.

:max-reprocesses

Optional positive integer internal reprocess limit.

:handshake-timeout

Optional TLS handshake timeout in milliseconds.

:max-spare-pipes

Optional non-negative idle pipe cache limit.

:http1-req-timeout

Optional HTTP/1.1 request timeout in milliseconds.

:http1-req-io-timeout

Optional HTTP/1.1 request I/O timeout in milliseconds.

:http1-upgrade?

Allow h2c upgrade, default true.

:http2-idle-timeout

Optional HTTP/2 idle timeout in milliseconds.

:http2-graceful-shutdown-timeout

Optional HTTP/2 graceful shutdown timeout in milliseconds.

:http2-max-streams

Optional positive integer concurrent HTTP/2 stream limit.

:http2-max-requests

Optional positive integer concurrent HTTP/2 request limit.

:http2-max-streaming-requests

Optional positive integer streaming request limit.

:http2-max-priority-streams

Optional positive integer priority stream tracking limit.

:http2-stream-window-size

Optional positive integer per-stream window bytes.

:http2-dos-delay

Optional suspicious behavior delay in milliseconds.

:http3-idle-timeout

Optional HTTP/3 idle timeout in milliseconds.

:http3-graceful-shutdown-timeout

Optional HTTP/3 graceful shutdown timeout in milliseconds.

:http3-stream-window-size

Optional positive integer per-stream window bytes.

:http3-ack-frequency

Optional non-negative ACK frequency; 0 uses the QUIC default.

:compress?

Enable response compression, default true.

:compress-min-size

Minimum response bytes for compression, default 100.

:compress-gzip-level

Gzip level 0 through 9, default 1.

:compress-brotli-level

Brotli level 0 through 11, default 1.

:compress-zstd-level

Zstandard compression level, default 3.

Entrypoints (:entrypoints)

Entrypoints describe how traffic enters Busker.

Each entrypoint gives a stable id to one or more bind addresses and the protocol and TLS settings used by those listeners. The id exists so other config sections can refer to the entrypoint without repeating host and port details.

:entrypoints in the top-level config is a map from keyword entrypoint ids to maps:

{:entrypoints {:plain {:bind ["127.0.0.1:8080" "[::1]:8080"]
                       :tls false}
               :secure {:bind ":8443"}}}

The ids (:plain and :secure above) are arbitrary user-defined keywords.

  • :bind is a string or non-empty vector of strings. TCP and UDP bind forms are ":8080", "host:8080", and "[::1]:8080". Unix domain sockets use pathname form "unix:/path.sock" or abstract namespace form "unix:@abstract-name".

  • :tls is false for cleartext or a map to enable TLS. A TLS entrypoint requires top-level :tls :certificates :load or :tls :certificates :manage. Entrypoint-local TLS settings use the same keys as top-level :tls, but certificate material remains top-level.

  • :http1? and :http2? default to true.

  • :http3? defaults to true when TLS is enabled and false otherwise. HTTP/3 requires TLS and is not supported on Unix domain sockets.

Dispatchers (:dispatch)

Dispatchers describe what Busker does with each request after it arrives.

Busker evaluates them in order, optionally restricting them by entrypoint, match predicate, group, middleware, and terminal behavior.

:dispatch in the top-level config is an ordered vector of dispatcher maps.

For conventional Clojure programs, the most common shape is a single dispatcher that points at one Ring handler. Start with this unless one server needs multiple entrypoint-specific or staged request pipelines.

{:dispatch [{:handler your-ring-handler}]}

Advanced use-cases can add entrypoint filters, match predicates, mutually exclusive groups, middleware, and terminal behavior. This is useful when using Busker as an application router or reverse-proxy-style edge, where one server routes different traffic classes before handing each request to the appropriate Ring handler or upstream:

{:dispatch [{:entrypoints #{:secure}
             :group :site
             :match my.app/api-request?
             :middleware [[my.app/wrap-audit {:mode :strict}]
                          my.app/wrap-auth]
             :handler my.app/handler
             :terminal? true}]}
  • :entrypoints - Optional set of entrypoint ids where the dispatcher may run (default: all entrypoints).

  • :group - Optional key that allows only the first matching dispatcher in the group (default: none).

  • :match - Optional predicate of the current request (default: match every request).

  • :middleware - Vector of middleware functions, symbols, or [middleware opts] entries (default: []).

  • :handler - Handler function for the dispatcher (default: {:status 200}).

  • :terminal? - Return the handler result immediately when true (default: false).

Function references in :match, :handler, and :middleware may be direct functions or qualified symbols.

Middleware entries are Ring-style wrapper functions. [wrap opts] calls (wrap handler opts), and a bare wrap calls (wrap handler).

Non-terminal dispatcher results become the current value for later dispatchers, so dispatchers can act as request transforms before a terminal handler returns a Ring response.

TLS (:tls, top-level)

The top-level TLS config describes how Busker terminates encrypted traffic, finds certificate material, and runs certificate automation for TLS entrypoints.

:tls in the top-level config is a map of options:

  • :certificates - Map of certificate sources and managed names; see below (default: none).

  • :storage - Storage instance or storage factory map; see Storage factory map below (default: Clave file storage for managed certificates).

  • :issuers - Vector of issuer maps; see Issuer maps below (default: Let’s Encrypt production).

  • :issuer-selection - ACME issuer choice strategy, :in-order or :shuffle (default: :in-order).

  • :key-type - Certificate key algorithm (default: :p256).

  • :key-reuse - Reuse private keys when renewing certificates (default: false).

  • :cache-capacity - Positive integer certificate cache capacity (default: 1000).

  • :solvers - ACME challenge solver map; see Solvers below (default: Busker-managed :http-01 for managed certificates).

  • :ocsp - OCSP option map; see OCSP map below (default: none).

  • :config-fn - Function or qualified symbol returning per-subject/per-domain overrides (default: none).

  • :http-client - HTTP client option map for ACME requests (default: none).

  • :tls-compatibility-mode - TLS cipher/protocol preset, :modern or :intermediate (default: :modern).

  • :session-tickets - Session ticket map; see Session ticket map below (default: enabled in memory).

Certificates map (:tls :certificates)

The :certificates map contains:

  • :load - Vector of static certificate source maps (default: []).

    • :type - Source type, either :pem or :folder (required).

    • :cert-file - PEM certificate chain path for :pem sources.

    • :key-file - PEM private key path for :pem sources.

    • :path - Directory path for :folder sources.

  • :manage - Vector of subject names (domain names) Busker should obtain and renew automatically (default: []).

    • Entries are strings such as "example.com" or "*.example.com".

Static sources in :load are read during startup and reload. Busker extracts subject names from each certificate and uses SNI to choose a matching static certificate during the TLS handshake. If there is no SNI match, Busker falls back to the first loaded static certificate.

Subject names in :manage are handed to Clave certificate automation. Busker starts the automation machinery during startup and reload, stores issued certificates in TLS storage, and uses SNI to look them up during TLS handshakes. Certificate lifecycle work such as obtain, renewal, and OCSP maintenance operates in the background.

Obtain certificates automatically
{:tls {:certificates {:manage ["example.com" "www.example.com"]}}}
Bring your own certificates
{:tls {:certificates {:load [{:type :pem
                              :cert-file "example.com/fullchain.pem"
                              :key-file "example.com/privkey.pem"}]}}}

Storage factory map (:tls :storage)

The :storage key may be a ol.clave.storage/Storage instance or a factory map.

When omitted, managed certificate automation uses ol.clave.storage.file/file-storage with its no-arg default root.

The Clave default is to use <dir>/ol.clave, where <dir> is $STATE_DIRECTORY (for systemd usage) or $XDG_DATA_HOME. While often not necessary, you can configure this explicitly.

When used as a map :storage contains:

  • :factory - Function or qualified symbol returning an ol.clave.storage/Storage implementation (required).

  • factory-specific keys - Options passed to the factory (default: none). For the built-in filesystem storage factory, see ol.clave.storage.file/file-storage. Its options include :root, a root directory string or java.nio.file.Path.

Issuer maps (:tls :issuers)

The :issuers vector contains maps:

  • :directory-url - ACME directory URL (required).

  • :email - Contact email for certificate notifications (default: none).

  • :eab - External account binding credentials for restricted CAs (default: none).

OCSP map (:tls :ocsp)

The :ocsp map contains:

  • :enabled? - Enable OCSP stapling (default: true).

  • :must-staple? - Require OCSP must-staple in certificates (default: false).

Session ticket map (:tls :session-tickets)

The :session-tickets map contains:

  • :disabled? - Disable session ticket resumption when true (default: false).

  • :persistence - Session ticket persistence mode, :memory or :storage (default: :memory).

    • :storage persistence requires top-level :tls :storage.

  • :max-keys - Maximum number of ticket keys retained in rotation (default: 4).

  • :lifetime-seconds - TLS session ticket lifetime in seconds (default: 86400).

Solvers (:tls :solvers)

Solvers describe how ACME challenge records are presented and cleaned up while Busker obtains managed certificates.

Busker provides an integrated :http-01 solver by default. It serves /.well-known/acme-challenge/…​ through the Ring handler pipeline. Busker does not provide :dns-01 or :tls-alpn-01 by default; add those to :solvers when needed.

The :solvers map contains additional Clave solver entries keyed by challenge type:

  • :http-01 - HTTP challenge solver for public HTTP validation. Busker always replaces the value of this key with its integrated solver.

  • :dns-01 - DNS challenge solver for DNS-provider validation. This is required for wildcard names such as "*.example.com".

  • :tls-alpn-01 - TLS ALPN challenge solver for port 443 validation.

To write a custom solver, see ol.clave.certificate and ol.clave.certificate/validate-solvers.

This example adds :dns-01, while Busker still adds its integrated :http-01 solver:

{:tls {:certificates {:manage ["example.com" "*.example.com"]}
       :solvers {:dns-01 my-dns-solver}}}