Configuration
The application server instance is controlled by a single JSON configuration file, usually named config.json.
You only need to include settings you want to change from their defaults - unspecified settings retain their default values.
Configuration values can be hardcoded in the configuration file or can be read from environment variables.
To use an environment variable, wrap the variable name in ${}. For example, "password": "${DB_PASSWORD}" will read the value from the DB_PASSWORD environment variable at startup.
Several sample configuration files are included in the release package as starting points:
config-sqlite-noauth.json- SQLite database, no authenticationconfig-sqlite-oidc.json- SQLite database, OIDC authenticationconfig-mssql-noauth.json- SQL Server database, no authenticationconfig-mssql-sqlauth.json- SQL Server database, SQL authenticationconfig-mssql-oidc.json- SQL Server database, OIDC authentication
Root Folder and Paths
Several folder paths are used by the application server.
All default paths and configured paths are relative to the root folder of the configuration file.
This can be overridden by specifying the root path in the paths.root configuration option or by specifying full path names in the individual configuration options.
Configuration Reference
Server (server)
| Setting | Default | Description |
|---|---|---|
server.hostname |
"" |
Hostname or IP address the server binds to. Empty binds to all interfaces. |
server.port |
80 |
HTTP listening port (defaults to 443 when HTTPS is active). |
server.certFile |
"" |
Path to TLS certificate file. HTTPS is activated when both cert and key files are present. Mutually exclusive with server.tls.acme. |
server.keyFile |
"" |
Path to TLS private key file. |
server.tls.acme.enabled |
false |
Obtain and auto-renew a certificate automatically over ACME / Let's Encrypt instead of a manual certFile/keyFile. See Automatic certificates (ACME / Let's Encrypt). |
server.tls.acme.domains |
[] |
Public hostnames the ACME certificate covers (at least one required when enabled). |
server.tls.acme.email |
"" |
Optional contact address registered with the ACME account. |
server.tls.acme.cacheDir |
"certs" |
Directory (relative to paths.root unless absolute) that caches issued certificates so restarts reuse them. |
server.tls.acme.acceptTOS |
false |
Must be true to agree to the CA's Terms of Service; the server refuses to start otherwise when ACME is enabled. |
server.tls.acme.directoryURL |
"" |
ACME directory endpoint. Blank = Let's Encrypt production; set the staging URL while testing. |
server.sessionMinutes |
540 |
Absolute session lifetime in minutes (default 9 hours). A session expires this long after login regardless of activity. |
server.idleMinutes |
240 |
Idle timeout in minutes: a session is logged out after this much inactivity (default 4 hours). 0 disables the idle check (only the absolute lifetime applies). The window slides on each request but never extends past sessionMinutes. |
server.trustedProxies |
[] |
CIDRs (or bare IPs) of front proxies trusted for the X-Forwarded-For header. Empty (default) ignores X-Forwarded-For and uses the direct connection IP. Set this when the server runs behind a reverse proxy (IIS/nginx/Cloudflare) so the real client IP is used for rate limiting and session IP binding. |
server.maxUpload |
32 |
Maximum file upload size in megabytes. |
server.permissionsPolicy |
(empty) | When set, sent verbatim as the Permissions-Policy response header (e.g. camera=(), microphone=(), geolocation=() to disable those browser features). Empty sends no header. See Security Response Headers. |
server.maxConcurrentRequests |
256 |
Maximum requests handled concurrently. When the in-flight count is at this cap, further requests are shed immediately with 503 Service Unavailable + Retry-After instead of queueing unboundedly, so a burst (legitimate or hostile) can't exhaust memory. 0 disables the cap. Size it to the host and keep it at or below what the database pool (database.maxOpenConns) can service. See Request and Connection Caps. |
server.maxConnections |
1024 |
Global cap on simultaneously accepted TCP connections. At the cap the server stops accepting new connections (graceful backpressure at the OS accept queue) until one closes. 0 disables the cap. |
server.maxConnectionsPerIP |
0 |
Cap on simultaneous connections from a single direct-peer IP; connections beyond it are closed. Off by default and intended only for direct public exposure: behind a reverse proxy every connection shares the proxy's IP, and a shared client-side NAT aggregates many users under one IP, so this cap would false-positive a busy office egress IP. Leave 0 when behind a proxy or when users share a NAT; enable and size it only for genuinely direct exposure. |
Uploads (uploads) - a site-wide filter applied to every uploaded file (database-stored and storage-provider-stored), checked against both the file extension and the sniffed content type before the bytes reach any SQL command or storage location. A rejected upload is treated as a save failure: the write method is skipped and only the GET runs (with the page's error message).
| Setting | Default | Description |
|---|---|---|
uploads.enabled |
true |
When false, all file uploads are rejected (and multipart file bytes are not read). |
uploads.allowedExtensions |
["*"] |
Site-wide extension allowlist. ["*"] (or omitted) allows all; otherwise only listed extensions pass. |
uploads.allowedMimeTypes |
["*"] |
Site-wide content-type allowlist. ["*"] (or omitted) allows all; otherwise only listed types pass. |
uploads.enforceBlocklist |
true |
When true, the dangerous-type denylist is enforced. Set to false to disable it entirely - this logs a strong SECURITY warning at startup. |
uploads.blockedExtensions |
(built-in) | Omitted = the built-in dangerous default (html, htm, svg, js, php, exe, …). A provided list replaces the default (logged as a custom denylist). |
uploads.blockedMimeTypes |
(built-in) | Omitted = the built-in dangerous default (text/html, image/svg+xml, application/javascript, …). A provided list replaces the default. |
Evaluation order per file: enabled → denylist (a match blocks; the denylist wins over the allowlist) → site-wide allowlist → the per-storage-location allowlist (provider saves only). Out of the box (no uploads block) uploads are enabled, all types are allowed except the built-in dangerous set - secure by default with no friction for PDFs, images, and Office documents. To allow a normally-blocked type, provide your own blockedExtensions/blockedMimeTypes omitting it.
How the extension and content-type checks combine. Every file is judged on two independent attributes: its extension (from the file name) and its content type (sniffed from the bytes, so a renamed file is still classified by its real type). The denylist and the allowlists combine those two attributes differently:
- Denylist - a file is blocked if either its extension is on
blockedExtensionsor its content type is onblockedMimeTypes. Only one needs to match (logical OR). This is deliberately aggressive so a disguised file is still caught: an HTML page renamed to.txtis blocked by its sniffedtext/html, and a file with an innocuous content type but an.exename is blocked by its extension. (A file with no extension can only be matched by content type; the empty extension never matches the denylist.) - Allowlists - both the site-wide
uploads.allowedExtensions/allowedMimeTypesand the per-locationstorage.locations.<name>.allowedExtensions/allowedMimeTypeswork the same way: a file must satisfy every allowlist that is configured (logical AND). If you set only an extension allowlist, only the extension is checked (content type is ignored); if you set only a content-type allowlist, only the content type is checked; if you set both, the file must match both - its extension on the extension allowlist and its content type on the content-type allowlist - and a mismatch on either one rejects it. A file with no extension fails any configured extension allowlist.
In short: the denylist blocks when any check matches; an allowlist permits only when every configured check matches; and because the denylist is evaluated first, a denied type can never be re-permitted by an allowlist. These same combination rules apply to the downloads and email.attachments filters below (they share the same configuration shape and built-in denylist).
Built-in denylist (used when blockedExtensions / blockedMimeTypes are omitted and enforceBlocklist is true; shared by the uploads, downloads, and email.attachments filters):
Extensions:
- Web-active content:
html,htm,shtml,xhtml,xht,svg,svgz,js,mjs,cjs,htc,hta - Server-side scripts:
php,php3,php4,php5,phtml,asp,aspx,jsp,jspx,cfm,cgi,pl,py,rb,sh - Executables / installers:
exe,dll,com,bat,cmd,scr,msi,msp,vbs,vbe,wsf,ws,ps1,jar,reg,lnk
Content types: text/html, application/xhtml+xml, image/svg+xml, text/javascript, application/javascript, application/x-javascript, application/ecmascript, application/x-msdownload, application/x-sh, application/x-httpd-php, application/java-archive
Providing your own blockedExtensions or blockedMimeTypes replaces the corresponding list above (it is not merged), so include the entries you still want to block.
Downloads (downloads) - the same filter shape (enabled, allowedExtensions, allowedMimeTypes, enforceBlocklist, blockedExtensions, blockedMimeTypes) applied to files served by the download and binary aliases, checked against the file's file_name and file_type before it is streamed (this also covers the S3 presigned-redirect path). Like uploads, the dangerous-type denylist is enforced by default, so a stored file with a blocked type is not served (it returns the page's error). downloads.enabled: false disables the download/binary aliases entirely. This is a second checkpoint behind the upload filter: even content that reached storage by another route (a migration, an external feed, or data predating the upload filter) is filtered on the way out. (Independently of this filter, the binary alias only ever renders inline known-safe types - images and PDF - and downgrades everything else to an attachment with nosniff; see Downloads from Database.)
Email attachments (email.attachments) - the same filter shape applied to outgoing email attachments (the rows of an attach_alias, checked by file_name/file_type). The denylist is enforced by default. When email.attachments.enabled is false, the email is still sent but its attachments are stripped (logged as a SECURITY event); when enabled, an attachment that fails the allow/deny lists fails the email alias (so a critical email alias halts and rolls back).
Paths (paths)
| Setting | Default | Description |
|---|---|---|
paths.root |
"" |
Root folder for all relative paths. Defaults to the directory containing config.json. |
paths.asset |
"assets" |
Static assets served at /asset. |
paths.lib |
"libs" |
Frontend libraries served at /lib. |
paths.pages |
"pages" |
HTMQL page files. |
paths.system |
"system" |
System files (header, footer, error pages, etc.). |
paths.templates |
"templates" |
Reusable HTML template files. |
paths.migrations |
"migrations" |
SQL migration files. |
Pages (pages)
| Setting | Default | Description |
|---|---|---|
pages.cache |
true |
When true, page source is held in memory. Independent of refreshScanSeconds. Always forced off in development mode. Combine cache: true with refreshScanSeconds: 0 to load all pages at startup and reload only on demand or restart. |
pages.refreshScanSeconds |
60 |
How often the pages directory is auto-rescanned for file changes. Set to 0 to disable auto-rescanning (caching is controlled separately by cache). |
pages.defaultMenuIcon |
(info icon) | Default SVG icon used for menu entries that do not specify one. See Menu. |
pages.hideUnauthorizedMenuItems |
true |
When true, the navigation menu omits entries the current user is not authorized to view (evaluated against each page's GET authorization rules). Set to false to always show every menu entry regardless of access. Has no effect when auth.type is none. |
pages.externalRedirects |
[] |
Allowlist of external hosts the redirect alias may send users to. Empty (default) allows same-site relative redirects only. Each entry is an exact host (payments.example.com, optionally with a port) or a full URL (https://payments.example.com) - include a scheme to also require it. Matching is exact-host (no subdomain wildcards). |
Database (database)
| Setting | Default | Description |
|---|---|---|
database.type |
"" |
Database engine: sqlite, mssql, or postgres. |
database.host |
"" |
Database server hostname (mssql and postgres only). |
database.port |
0 |
Database server port. Defaults to 1433 for mssql, 5432 for postgres. |
database.name |
"" |
Database name, or file path for SQLite. |
database.user |
"" |
Database username. |
database.password |
"" |
Database password. |
database.encryption |
false |
Require an encrypted connection. |
database.verifyEncryption |
false |
Verify the server certificate when using an encrypted connection. |
database.maxOpenConns |
25 |
Maximum simultaneous database connections (and therefore concurrent queries). Excess requests wait for a free connection. 0 = unlimited (not recommended - an unbounded pool can overload the database engine). |
database.queryTimeoutSeconds |
30 |
Per-query deadline, covering both execution and reading the result rows. A query that exceeds it is cancelled at the database and the page shows an error. 0 disables the timeout. |
database.maxResultMB |
256 |
Aborts a single query whose result would build more than this much in memory (approximate), protecting the server from an unbounded SELECT. The page shows an error suggesting a WHERE/LIMIT clause. 0 disables the guard. The figure is an estimate of retained memory; actual heap is somewhat higher, so keep it comfortably below available RAM. Raise it if you have a server with ample RAM and legitimately large result sets. |
Migrations (migrations)
| Setting | Default | Description |
|---|---|---|
migrations.enabled |
false |
Run SQL migration files automatically on startup. |
migrations.table |
"htmql_migrations" |
Database table used to track applied migrations. |
migrations.validateChecksums |
true |
Verify that previously applied migration files have not been modified. |
migrations.allowMissingAppliedFiles |
false |
Allow startup to continue if a previously applied migration file is no longer on disk. |
Authentication (auth)
| Setting | Default | Description |
|---|---|---|
auth.type |
(required) | Authentication type: none, sql, table, or oidc. Server will not start if not set. |
auth.defaultAllow |
false |
Allow access to pages with no role decorators when true, deny when false. |
auth.denyPriority |
true |
Deny roles take priority over allow roles when a user matches both. |
auth.adminRole |
"" |
Optional role that overrides all page authorization and satisfies auth()/notauth(). Also gates the Admin Management Page. Empty disables both. |
auth.serviceAccounts |
[] |
Non-session callers (tools, jobs) authenticated by a shared secret in the X-HTMQL-Service-Token header. Each entry has name, secret (${VAR} ok, constant-time compared, never logged), roles, and allowRemote (default false = loopback only). Resolves to a normal authenticated principal; page policy still applies. See Service-credential authentication. |
auth.sessionIPBinding.enabled |
false |
When true, a session is softly bound to the network block of the IP it was created from. A request from a different block forces re-authentication (the session is dropped and the user is bounced to login) rather than being hard-killed. Off by default. Requires server.trustedProxies to be set correctly when behind a reverse proxy, otherwise every client appears to share the proxy IP. |
auth.sessionIPBinding.ipv4Bits |
16 |
Prefix length compared for IPv4 clients. Larger = stricter. 0 disables the IPv4 check. |
auth.sessionIPBinding.ipv6Bits |
48 |
Prefix length compared for IPv6 clients. /48 tolerates normal privacy-extension address rotation within a delegated prefix. 0 disables the IPv6 check. |
auth.oidc.* |
- | OIDC provider settings. See OIDC Authentication. |
auth.local.* |
- | Local role table settings. See Role Assignment. |
Admin (admin)
| Setting | Default | Description |
|---|---|---|
admin.route |
/admin |
Base URL for the built-in Admin Management Page. Disabled unless auth.adminRole is also set. |
admin.page |
admin.htmql |
Admin page template, relative to the system folder unless absolute. Override the file to customize the page. |
External Execution (execute)
| Setting | Default | Description |
|---|---|---|
execute.enabled |
false |
Enable the execute alias. Must be explicitly set to true. |
execute.timeoutSeconds |
30 |
Maximum time in seconds to wait for an executable to complete. |
execute.maxOutputBytes |
65536 |
Maximum bytes captured from stdout. |
execute.allowOnGet |
false |
Allow execute to run during GET requests. Defaults to POST/PUT/PATCH/DELETE only. |
execute.allowedExecutables |
[] |
Whitelist of allowed executable paths. An empty list allows any executable. |
Rate Limiting (rateLimit)
| Setting | Default | Description |
|---|---|---|
rateLimit.htmxErrorPath |
"" |
URL to redirect the browser to when an HTMX request is rate limited. Leave blank to suppress the redirect and return only a 429 response. |
rateLimit.ip.enabled |
false |
Enable per-IP rate limiting across all requests. |
rateLimit.ip.maxRequests |
300 |
Maximum requests allowed from a single IP within the window. |
rateLimit.ip.windowSeconds |
60 |
Sliding window duration in seconds. |
rateLimit.ip.blockDurationSeconds |
120 |
How long in seconds to block an IP after the limit is exceeded. |
rateLimit.duplicateRequest.enabled |
false |
Enable per-IP + URL rate limiting. Catches the same request repeating rapidly from the same IP (e.g. an execute callback loop). |
rateLimit.duplicateRequest.maxRequests |
30 |
Maximum times the same IP + URL combination is allowed within the window. |
rateLimit.duplicateRequest.windowSeconds |
60 |
Sliding window duration in seconds. |
rateLimit.duplicateRequest.blockDurationSeconds |
300 |
How long in seconds to block the IP + URL combination after the limit is exceeded. |
rateLimit.unauthenticated.enabled |
true |
Enable per-IP rate limiting of unauthenticated requests only (requests with no valid session). On by default as an anonymous-flood guard for public exposure. Counts only page requests — static assets (/asset, /lib, /favicon.ico) are never counted. Keyed by client IP; set server.trustedProxies so the key is the real client behind a proxy. |
rateLimit.unauthenticated.maxRequests |
600 |
Maximum unauthenticated requests allowed from a single IP within the window. Sized to clear a morning-rush of ~50+ OIDC users behind one NAT IP with headroom; validate against real logs and tune. |
rateLimit.unauthenticated.windowSeconds |
60 |
Sliding window duration in seconds. |
rateLimit.unauthenticated.blockDurationSeconds |
300 |
How long in seconds to block an unauthenticated IP after the limit is exceeded (default 5 minutes). Keep modest so a misjudged limit self-heals. |
rateLimit.authenticated.enabled |
false |
Enable rate limiting of authenticated requests, keyed on the session identity (username) rather than IP — so it is NAT-immune and each user is limited independently. Off by default (authenticated abuse is rare); safe to enable. |
rateLimit.authenticated.maxRequests |
1200 |
Maximum authenticated requests allowed per user within the window. |
rateLimit.authenticated.windowSeconds |
60 |
Sliding window duration in seconds. |
rateLimit.authenticated.blockDurationSeconds |
120 |
How long in seconds to block a user after the limit is exceeded (default 2 minutes). |
rateLimit.login.enabled |
true |
Enable tighter rate limiting on the login page. On by default for brute-force protection; applied in addition to the IP limiter. Only counts POST login attempts (GET page-views never consume the budget). Keyed by client IP, so set to false if the server sits behind an aggregating reverse proxy where all clients share one source IP. |
rateLimit.login.maxRequests |
30 |
Maximum login attempts (POST) allowed from a single IP within the window. |
rateLimit.login.windowSeconds |
300 |
Sliding window duration in seconds (default 5 minutes). |
rateLimit.login.blockDurationSeconds |
600 |
How long in seconds to block an IP from the login page after the limit is exceeded (default 10 minutes). |
PDF (pdf)
| Setting | Default | Description |
|---|---|---|
pdf.enabled |
true |
Enable PDF generation. |
pdf.chromePath |
"" |
Path to Chrome/Chromium/Edge executable. Auto-detected when empty. |
pdf.timeoutSeconds |
30 |
Maximum time in seconds to wait for PDF generation. |
pdf.marginInches |
0.5 |
Default page margin in inches. |
pdf.marginCms |
0 |
Default page margin in centimetres. Overrides marginInches when nonzero. |
pdf.renderHostname |
"" |
Hostname the server's TLS certificate is issued for. During PDF generation, headless Chrome fetches the page's /asset and /lib resources from the server; when TLS is enabled it requests them against this hostname so the certificate verifies, while the connection is mapped back to the loopback interface so traffic never leaves the machine. Leave blank to use the listen address (127.0.0.1), which only works if the certificate covers that IP. |
pdf.insecureSkipVerify |
false |
Disable TLS certificate verification for the asset fetches headless Chrome makes during PDF generation. Escape hatch for self-signed certificates that cannot be added to the machine trust store; prefer pdf.renderHostname. |
TLS note: When the server runs over HTTPS, headless Chrome fetches the page's
/assetand/libresources from the server during PDF generation. Setpdf.renderHostnameto the name on your certificate so those fetches verify cleanly; otherwise they fail withx509: cannot validate certificate for 127.0.0.1 because it doesn't contain any IP SANs. As a last resort, setpdf.insecureSkipVerifytotrue.
Email (email)
| Setting | Default | Description |
|---|---|---|
email.enabled |
false |
Enable the email alias. Must be explicitly set to true. |
email.smtp.host |
"" |
SMTP server hostname. Required when using smtp mode. |
email.smtp.port |
587 |
SMTP server port. |
email.smtp.username |
"" |
SMTP authentication username (also used as the MAIL FROM address). |
email.smtp.password |
"" |
SMTP authentication password. |
email.smtp.from |
"" |
Display address for the From: header (e.g. "App Name <noreply@example.com>"). Required for smtp mode. |
email.smtp.tlsMode |
"starttls" |
TLS mode: starttls (port 587), tls (implicit TLS, port 465), or none. |
For m365_draft and m365_send modes, no email.smtp settings are needed. Instead, add the required Microsoft Graph scope to auth.oidc.scopes:
Mail.ReadWrite- required form365_draft(create a draft the user can review and send)Mail.Send- required form365_send(send immediately on behalf of the user)
See Email Sending for full details.
Storage (storage)
Named locations for the save, download, and binary aliases. See Storage Locations for usage. Limit settings (maxUploadMB, allowedExtensions, allowedMimeTypes) may be set at the storage level as defaults and overridden per location.
| Setting | Default | Description |
|---|---|---|
storage.default |
(auto) | Default location name. Omit to auto-pick when one location exists; "" disables the default (making storage_location mandatory). |
storage.maxUploadMB |
server.maxUpload |
Default per-location upload cap in MB. May not exceed server.maxUpload. |
storage.allowedExtensions |
[] |
Default allowed file extensions (empty = allow all). |
storage.allowedMimeTypes |
[] |
Default allowed MIME types (empty = skip the MIME check). |
storage.locations.<name>.type |
(required) | local, s3, or sftp. |
storage.locations.<name>.default |
false |
Marks this location as the default. |
storage.locations.<name>.basePath |
"" |
(local) Root directory for stored files. Relative to paths.root unless absolute. (sftp) Remote root prepended to every file_path; leave empty when file_path is already an absolute remote path. |
storage.locations.<name>.fileMode |
"" |
(local, Unix/macOS only) Octal mode (e.g. "0600") for files written to this location. Empty uses the OS default. Ignored on Windows. |
storage.locations.<name>.dirMode |
"" |
(local, Unix/macOS only) Octal mode (e.g. "0700") for directories created under this location. Empty uses the OS default. Ignored on Windows. |
storage.locations.<name>.user |
"" |
(local, Unix/macOS only) Owner username for files/directories created here (chown; needs the privilege). Empty leaves ownership unchanged. Ignored on Windows. |
storage.locations.<name>.group |
"" |
(local, Unix/macOS only) Owner group for files/directories created here. Empty leaves ownership unchanged. Ignored on Windows. |
storage.locations.<name>.host |
"" |
(sftp) Server host name. Use ${ENV} to load from the environment. |
storage.locations.<name>.port |
22 |
(sftp) Server port. |
storage.locations.<name>.username |
"" |
(sftp) SSH user. Use ${ENV} to load from the environment. |
storage.locations.<name>.privateKeyPath |
"" |
(sftp) Path to the PEM private key for key-based auth. Use ${ENV}. At least one of privateKeyPath / password is required; when both are set, both auth methods are offered. |
storage.locations.<name>.privateKeyPassphrase |
"" |
(sftp) Passphrase for an encrypted private key, if any. Use ${ENV}. |
storage.locations.<name>.password |
"" |
(sftp) Password for password-based auth. Use ${ENV}. Alternative to privateKeyPath. |
storage.locations.<name>.knownHostsPath |
"" |
(sftp) Path to a known_hosts file used to verify the server's host key. Set this or hostKey to defend against MITM. |
storage.locations.<name>.hostKey |
"" |
(sftp) A single pinned server public key in authorized_keys format (e.g. ssh-ed25519 AAAA...). Alternative to knownHostsPath. |
storage.locations.<name>.insecureSkipHostKeyCheck |
false |
(sftp) Explicitly disable host key verification (not recommended). If none of knownHostsPath / hostKey / this is set, the location fails to start. |
storage.locations.<name>.endpoint |
"" |
(s3) Host (e.g. s3.amazonaws.com, or a MinIO/R2 endpoint). |
storage.locations.<name>.region |
"" |
(s3) Bucket region. |
storage.locations.<name>.bucket |
"" |
(s3) Bucket name. |
storage.locations.<name>.prefix |
"" |
(s3) Key prefix prepended to every file_path. |
storage.locations.<name>.accessKeyId |
"" |
(s3) Access key. Use ${ENV} to load from the environment. |
storage.locations.<name>.secretAccessKey |
"" |
(s3) Secret key. Use ${ENV} to load from the environment. |
storage.locations.<name>.useSSL |
false |
(s3) Use HTTPS for the endpoint. When false (or the endpoint is http://), object data and credentials travel in cleartext - the server logs a SECURITY warning at startup. Use HTTPS in production. |
storage.locations.<name>.pathStyle |
false |
(s3) Use path-style addressing (often required for MinIO/older stores). |
storage.locations.<name>.publicUrlBase |
"" |
(s3) Base URL used to build @save_url (e.g. a CDN domain). |
storage.locations.<name>.presignTTLSeconds |
900 |
(s3) Validity window, in seconds, for a presigned download URL (the delivery=redirect path). Defaults to 15 minutes. |
storage.locations.<name>.maxUploadMB |
(inherits) | Per-location upload cap in MB. |
storage.locations.<name>.allowedExtensions |
(inherits) | Per-location allowed extensions. |
storage.locations.<name>.allowedMimeTypes |
(inherits) | Per-location allowed MIME types. |
Logging (logging)
| Setting | Default | Description |
|---|---|---|
logging.http |
false |
Log all HTTP page requests. |
logging.forms |
false |
Log form field values submitted with requests (sensitive fields like password/token are redacted). |
logging.queries |
false |
Log all SQL queries sent to the database. |
logging.transactions |
false |
Log SQL transaction start, commit, and rollback events. |
logging.skipped |
false |
Log SQL commands skipped due to missing parameters. |
logging.caching |
false |
Log page cache scan and reload events. |
logging.session |
false |
Log session creation and expiry. |
logging.execute |
false |
Log external executable events |
logging.tlsHandshakeErrors |
false |
Log TLS handshake errors (e.g. IP scanners probing port 443, or an ACME domain missing from the whitelist). Off by default because these are benign and constant on a public host; when on they are logged at INFO, not ERROR, so they never inflate error-rate alerting. |
logging.file.path |
"htmql-server.log" |
Log file path. |
logging.file.maxSizeMB |
10 |
Maximum log file size in MB before rotation. |
logging.file.maxBackups |
10 |
Number of rotated log files to retain. |
logging.file.fileMode |
"" |
Unix/macOS only. Octal mode (e.g. "0600") for the log file. Empty uses the OS default. Ignored on Windows. |
logging.file.dirMode |
"" |
Unix/macOS only. Octal mode (e.g. "0700") for the log directory. Empty uses the OS default. Ignored on Windows. |
logging.file.user |
"" |
Unix/macOS only. Owner username for the log file/directory (chown; needs the privilege to change ownership). Empty leaves ownership unchanged. Ignored on Windows. |
logging.file.group |
"" |
Unix/macOS only. Owner group for the log file/directory. Empty leaves ownership unchanged. Ignored on Windows. |
Analytics (analytics)
| Setting | Default | Description |
|---|---|---|
analytics.enabled |
false |
Log one row per page request to the database. See Page-View Logging. |
analytics.table |
"page_view" |
Destination table for page-view rows. You must create this table (e.g. via a migration). |
analytics.ignorePaths |
[] |
URL path prefixes to exclude from logging (e.g. ["/health", "/status"]). |
Formatting (formatting)
| Setting | Default | Description |
|---|---|---|
formatting.shortDate |
"2006-01-02" |
Short date format. |
formatting.longDate |
"Mon Jan 2 2006" |
Long date format. |
formatting.time |
"3:04pm" |
Time format. |
formatting.shortDateTime |
"2006-01-02 3:04pm" |
Short date and time format. |
formatting.longDateTime |
"Mon Jan 2 2006 3:04pm" |
Long date and time format. |
formatting.numbers |
"0,000" |
Integer number format. |
formatting.decimals |
"0,000.0" |
Decimal format. |
formatting.longDecimals |
"0,000.0000" |
Long decimal format. |
formatting.money |
"$0,000.00" |
Currency format. |
Default Messages (strings)
| Setting | Default | Description |
|---|---|---|
strings.success |
"Your changes have been saved." |
Default success message for write operations. |
strings.error |
"Your changes were not saved." |
Default error message for critical write failures. |
strings.geterror |
"There was a problem retrieving data." |
Default error message for GET failures. |
Development Mode
| Setting | Default | Description |
|---|---|---|
development |
false |
Show technical error details in the browser, enable all logging, and disable page source caching (read pages from disk every request). See Development Mode. |