Configuration
Every appsettings.json key for Lacuna Bulk Signer — type, default, environment-variable override,
and whether it is required.
Configuration sources, in precedence order
Later sources override earlier ones:
appsettings.json(built-in defaults)appsettings.{Environment}.json(e.g.appsettings.Production.json)appsettings.json+appsettings.{Environment}.jsonfound underBULK_SIGNER_CONFIG_DIR- Environment variables (
Section__Sub__Key)
The BULK_SIGNER_CONFIG_DIR step is what lets the binary live in a read-only install location
(/opt/bulksigner, %ProgramFiles%\Lacuna\BulkSigner) while the operator-edited production config
lives elsewhere (/etc/bulksigner, %ProgramData%\Lacuna\BulkSigner\config). The install scripts
set this variable; if you change install paths, update the variable in lockstep.
Environment-variable mapping follows the ASP.NET Core rule: a JSON key like
Signing:Certificate:Pfx:Password maps to Signing__Certificate__Pfx__Password (double underscore
is the separator).
Markers used in the tables
| Marker | Meaning |
|---|---|
| REQUIRED | The service refuses to start (or signing refuses to run) without a non-empty value. |
| SECRET | Sensitive — prefer the environment-variable override over a value committed to a file. |
Logging / Logging:File
Standard Microsoft.Extensions.Logging knobs (Logging:LogLevel:*) work as usual; the
Logging:File block configures the file sink.
| Key | Type | Default | Env override | Notes |
|---|---|---|---|---|
Logging:LogLevel:Default | string | Information | Logging__LogLevel__Default | Standard logging level. |
Logging:LogLevel:Microsoft.AspNetCore | string | Warning | Logging__LogLevel__Microsoft.AspNetCore | Lowers framework chatter. |
Logging:File:Path | string | data/logs/bulksigner-.log | Logging__File__Path | REQUIRED. File-sink path template. The trailing - before .log plus daily rolling produces bulksigner-yyyyMMdd.log. |
Logging:File:RollingInterval | string | Day | Logging__File__RollingInterval | One of Day, Hour, Minute, Infinite. |
Logging:File:FileSizeLimitBytes | long | 50000000 | Logging__File__FileSizeLimitBytes | Per-file cap; the sink rolls to …_001.log past this. Bounds: 64 KB to 10 GB. |
Logging:File:RetainedFileCountLimit | int | 14 | Logging__File__RetainedFileCountLimit | Older files are deleted as rotation advances. Bounds: 1–365. |
Logging:File:MinimumLevel | string | Information | Logging__File__MinimumLevel | One of Verbose, Debug, Information, Warning, Error, Fatal. |
Logging:File:WriteToConsole | bool | true | Logging__File__WriteToConsole | When true, also writes to stdout. The same redacting formatter runs on both sinks. |
ConnectionStrings
| Key | Type | Default | Env override | Notes |
|---|---|---|---|---|
ConnectionStrings:Default | string | Data Source=data/db/bulksigner.db | ConnectionStrings__Default | REQUIRED. SQLite connection string. Point at a path under Storage:Root so one mount covers both data and DB. |
Signing
Validation fails fast at startup if any required key is missing or invalid.
| Key | Type | Default | Env override | Notes |
|---|---|---|---|---|
Signing:PkiSdkLicense | string | "" | Signing__PkiSdkLicense | REQUIRED, SECRET. Lacuna PKI SDK license string (base64). Environment-variable form preferred. |
Signing:Certificate:Source | enum | Pfx | Signing__Certificate__Source | REQUIRED. One of Pfx, Pkcs11, WindowsStore, AzureKeyVault. Only the matching subtree below is consulted. |
Signing:Certificate:Pfx — when Source = Pfx
| Key | Type | Default | Env override | Notes |
|---|---|---|---|---|
Signing:Certificate:Pfx:Path | string | "" | Signing__Certificate__Pfx__Path | REQUIRED. Absolute path to the .pfx/.p12 file. |
Signing:Certificate:Pfx:Password | string | "" | Signing__Certificate__Pfx__Password | SECRET. Empty string is allowed for passwordless test fixtures. Prefer the env-var form. |
Signing:Certificate:Pkcs11 — when Source = Pkcs11
| Key | Type | Default | Env override | Notes |
|---|---|---|---|---|
Signing:Certificate:Pkcs11:ModulePath | string | "" | Signing__Certificate__Pkcs11__ModulePath | REQUIRED. Absolute path to the vendor PKCS#11 driver (.so/.dll/.dylib). |
Signing:Certificate:Pkcs11:Thumbprint | string | "" | Signing__Certificate__Pkcs11__Thumbprint | REQUIRED. SHA-1 thumbprint (hex, no spaces) of the signing cert on the token. Required even when the token holds a single identity. |
Signing:Certificate:Pkcs11:PinEnvVar | string | BULK_SIGNER_PKCS11_PIN | Signing__Certificate__Pkcs11__PinEnvVar | Name of the env var that supplies the PIN. The validator refuses to start if a literal Pin key appears under Pkcs11. |
Signing:Certificate:WindowsStore — when Source = WindowsStore
Windows-only. The validator refuses this source on non-Windows hosts at startup.
| Key | Type | Default | Env override | Notes |
|---|---|---|---|---|
Signing:Certificate:WindowsStore:StoreLocation | string | CurrentUser | Signing__Certificate__WindowsStore__StoreLocation | CurrentUser or LocalMachine. Use LocalMachine when the cert was imported machine-wide; the service account does not see the operator's CurrentUser store. |
Signing:Certificate:WindowsStore:StoreName | string | My | Signing__Certificate__WindowsStore__StoreName | Logical store name. My is the personal store. |
Signing:Certificate:WindowsStore:Thumbprint | string | "" | Signing__Certificate__WindowsStore__Thumbprint | REQUIRED. SHA-1 thumbprint (hex, no spaces). |
Signing:Certificate:AzureKeyVault — when Source = AzureKeyVault
The private key stays in the vault and each signature is a remote sign call; the public certificate
is a local .cer file. All five keys are required — startup fails naming each missing one.
| Key | Type | Default | Env override | Notes |
|---|---|---|---|---|
Signing:Certificate:AzureKeyVault:Endpoint | string | "" | Signing__Certificate__AzureKeyVault__Endpoint | REQUIRED. Vault URL. Must be an absolute https:// URL — a bare DNS name is refused at startup. |
Signing:Certificate:AzureKeyVault:AppId | string | "" | Signing__Certificate__AzureKeyVault__AppId | REQUIRED. Application (client) ID of the Microsoft Entra ID app registration. |
Signing:Certificate:AzureKeyVault:AppSecret | string | "" | Signing__Certificate__AzureKeyVault__AppSecret | REQUIRED, SECRET. Entra ID client secret. Unlike the PKCS#11 PIN this is permitted in a config file, but the env-var form is recommended. |
Signing:Certificate:AzureKeyVault:KeyName | string | "" | Signing__Certificate__AzureKeyVault__KeyName | REQUIRED. Name of the key object in the vault that performs the signature. A vault certificate object is not accepted. |
Signing:Certificate:AzureKeyVault:CerPath | string | "" | Signing__Certificate__AzureKeyVault__CerPath | REQUIRED. Path to the .cer holding the public certificate for KeyName. Boot fails if its public key does not match the vault key. |
See Certificates for thumbprint discovery commands, the Azure setup walkthrough, and a deeper look at each source.
Signing:Profiles[] — per-folder signing profiles
A signing profile bundles every per-folder decision (format, certificate, verify, encrypt,
certificate validation) under a name. Watched folders reference the profile by name via
Storage:Inputs[].Profile. Two configuration modes are supported:
- Legacy mode (default —
Signing:Profiles[]omitted or empty). The service synthesises one profile nameddefaultfrom the existingSigning:Certificateblock. No config changes are needed for a simple single-certificate install. - Profile mode (declare
Signing:Profiles[]). Each entry is a named profile with its own certificate and posture.Signing:Certificateis ignored. Every entry validates as if it were the global certificate block — the samePfx/Pkcs11/WindowsStore/AzureKeyVaultrules apply per profile.
| Key | Type | Default | Env override | Notes |
|---|---|---|---|---|
Signing:Profiles[].Name | string | n/a | Signing__Profiles__0__Name | REQUIRED. Same regex as folder names: ^[a-z0-9](?:[a-z0-9-]{0,38}[a-z0-9])?$. Unique across the list. The name is referenced from Storage:Inputs[].Profile and surfaces in metric labels, dashboard chips, and audit messages. |
Signing:Profiles[].Format | enum | n/a | Signing__Profiles__0__Format | REQUIRED for every operator-declared profile: Pades, Cades, Xades, or XmlNFe. Only the synthesised legacy default may leave this unset — in which case the format is detected per file by extension. |
Signing:Profiles[].Method | enum | Local | Signing__Profiles__0__Method | Local (sign with the configured local certificate) or LacunaSigner (dispatch to Lacuna Signer for a human participant). See Lacuna Signer integration. |
Signing:Profiles[].Verify | bool | true | Signing__Profiles__0__Verify | When false, the worker skips the post-sign verification round-trip. The startup banner emits a warning so the low-trust posture is operator-visible. |
Signing:Profiles[].Encrypt | bool | false | Signing__Profiles__0__Encrypt | When true, the worker AES-256-GCM-encrypts the signed output. Requires Encryption:Enabled = true (the validator refuses the broken combination at startup). |
Signing:Profiles[].ValidateCertificate | bool | true | Signing__Profiles__0__ValidateCertificate | When false, the worker skips the pre-sign certificate chain / revocation check. The startup banner emits a warning. Must be false when Method = LacunaSigner — there is no local cert to validate. |
Signing:Profiles[].Certificate.* | nested | n/a | Signing__Profiles__0__Certificate__… | REQUIRED when Method = Local. Same shape as the global Signing:Certificate block. Each profile loads its own certificate at boot; misconfiguration on any profile fails startup with an aggregated error. Refused when Method = LacunaSigner. |
Signing:Profiles[].Signer.Name | string | n/a | Signing__Profiles__0__Signer__Name | REQUIRED when Method = LacunaSigner. Display name of the participant Lacuna Signer will send the document to. |
Signing:Profiles[].Signer.Email | string | n/a | Signing__Profiles__0__Signer__Email | REQUIRED when Method = LacunaSigner. Participant's email address — must contain @. |
Signing:Profiles[].Signer.Identifier | string | n/a | Signing__Profiles__0__Signer__Identifier | REQUIRED when Method = LacunaSigner. Participant's national identifier (CPF in Brazil). |
Storage:Inputs[].Profile — per-folder routing
| Key | Type | Default | Env override | Notes |
|---|---|---|---|---|
Storage:Inputs[].Profile | string? | null (→ "default") | Storage__Inputs__0__Profile | Optional. References a Signing:Profiles[].Name. Null or empty falls back to the default profile. Unknown names fail validation at startup. |
Example: three profiles routed by folder, one per certificate source
"Signing": {
"PkiSdkLicense": "<env-var>",
"Profiles": [
{
"Name": "nfe",
"Format": "Xades",
"Verify": true,
"Encrypt": false,
"ValidateCertificate": true,
"Certificate": {
"Source": "Pkcs11",
"Pkcs11": { "ModulePath": "/usr/lib/x86_64-linux-gnu/pkcs11/libsofthsm2.so", "Thumbprint": "...", "PinEnvVar": "BULK_SIGNER_PKCS11_PIN" }
}
},
{
"Name": "contracts",
"Format": "Pades",
"Verify": true,
"Encrypt": true,
"ValidateCertificate": true,
"Certificate": {
"Source": "Pfx",
"Pfx": { "Path": "/etc/bulksigner/contracts.pfx", "Password": "" }
}
},
{
"Name": "invoices",
"Format": "Pades",
"Verify": true,
"Encrypt": false,
"ValidateCertificate": true,
"Certificate": {
"Source": "AzureKeyVault",
"AzureKeyVault": {
"Endpoint": "https://my-vault.vault.azure.net/",
"AppId": "8f2c1b3e-1111-2222-3333-444455556666",
"AppSecret": "",
"KeyName": "bulk-signer-invoices-key",
"CerPath": "/etc/bulksigner/certificates/invoices.cer"
}
}
}
]
},
"Storage": {
"Root": "/var/lib/bulksigner",
"Inputs": [
{ "Name": "nfe-incoming", "Path": "/var/lib/bulksigner/input-nfe", "Profile": "nfe" },
{ "Name": "contracts-incoming", "Path": "/var/lib/bulksigner/input-contracts", "Profile": "contracts" },
{ "Name": "invoices-incoming", "Path": "/var/lib/bulksigner/input-invoices", "Profile": "invoices" }
]
}
The invoices profile leaves AppSecret empty in the file and takes it from the environment
instead. Array elements are bound by positional index, so the secret for the third profile is:
export Signing__Profiles__2__Certificate__AzureKeyVault__AppSecret='…'
That index is positional, not name-based. Inserting a new profile above invoices shifts it to
index 3, the index-2 variable stops reaching it, and startup fails with
Signing:Profiles[3].Certificate.AzureKeyVault.AppSecret is required. Re-check every indexed
environment variable after reordering the list.
The startup banner lists every resolved profile with its format, certificate source, and
verify/encrypt/validate-cert flags. Profiles with Verify=false or ValidateCertificate=false
emit additional warnings so the low-trust posture is captured in durable logs.
Signer — Lacuna Signer connection
One Lacuna Signer tenant per host — the endpoint and API key are global, not per-profile. The
validator is self-gating: it only enforces Endpoint + ApiKey when at least one
Signing:Profiles[] entry has Method = LacunaSigner. Local-only deployments don't need to
configure any of this. See Lacuna Signer integration.
| Key | Type | Default | Env override | Notes |
|---|---|---|---|---|
Signer:Endpoint | string | "" | Signer__Endpoint | REQUIRED when any profile uses LacunaSigner. Base URL for the Lacuna Signer instance. Cloud default: https://signer.lacunasoftware.com. On-prem deployments point at the customer's instance. |
Signer:ApiKey | string | "" | Signer__ApiKey | REQUIRED, SECRET when any profile uses LacunaSigner. Expected shape: application-id|secret. The literal value is scrubbed from logs. |
Signer:PollIntervalSeconds | int | 30 | Signer__PollIntervalSeconds | How often the poll worker walks every AwaitingSigner row. Bounds: 1–3600. |
Signer:TimeoutHours | int | 168 (7 days) | Signer__TimeoutHours | How long a job may sit in AwaitingSigner before it is failed with code = signer.timeout. Bounds: 1–8760. |
Signer:MaxConsecutiveApiFailures | int | 5 | Signer__MaxConsecutiveApiFailures | Per-document consecutive transient-error budget before the poll worker gives up on that document. In-memory counter — restart resets it. |
Encryption
Off by default. The validator runs only when Enabled = true.
| Key | Type | Default | Env override | Notes |
|---|---|---|---|---|
Encryption:Enabled | bool | false | Encryption__Enabled | Master switch. When true, the worker AES-256-GCM-encrypts the signed artifact between verify and promote. |
Encryption:Password | string | "" | Encryption__Password | SECRET. PBKDF2 password. Allowed in config (use appsettings.Production.json, which is gitignored) but env-var form is preferred. |
Encryption:PasswordEnvVar | string | BULK_SIGNER_ENCRYPTION_PASSWORD | Encryption__PasswordEnvVar | Name of the env var that supplies the password. If non-empty at boot, it overrides Encryption:Password. |
Encryption:Salt | string | "" | Encryption__Salt | REQUIRED when Enabled = true. Base64-encoded PBKDF2 salt; must decode to at least 16 bytes. Salts are not secret. Changing the salt invalidates every prior envelope. |
Encryption:Iterations | int | 600000 | Encryption__Iterations | PBKDF2-HMAC-SHA256 iteration count. Rejected below 10000. |
Password loss is unrecoverable — there is no server-side decrypt endpoint and no escrow. See Encryption.
Auth
| Key | Type | Default | Env override | Notes |
|---|---|---|---|---|
Auth:ApiKey | string | "" | Auth__ApiKey | REQUIRED, SECRET. Static API key, minimum 16 characters. Sent in the X-API-Key header by programmatic clients; pasted at /login by operators to receive a cookie. |
Auth:CookieName | string | lbs-auth | Auth__CookieName | Cookie name issued by /api/auth/login. SameSite=Strict, HttpOnly, secure when the request was HTTPS. |
Auth:ApiKeyHeader | string | X-API-Key | Auth__ApiKeyHeader | HTTP header the API-key scheme reads. Rename only if a reverse-proxy convention forces it. |
See Security for API-key rotation and cookie session lifetime.
Storage
| Key | Type | Default | Env override | Notes |
|---|---|---|---|---|
Storage:Root | string | data | Storage__Root | REQUIRED. Root under which processing/, output/, error/, db/, logs/ are created. Override per target — /var/lib/bulksigner on Linux, C:\ProgramData\Lacuna\BulkSigner\data on Windows, /var/lib/bulksigner in Docker. |
Storage:Inputs[] | array of {Name, Path, IgnoredExtensions?, IgnoredPrefixes?, Profile?} | [{Name="default", Path="{Root}/input"}] | Storage__Inputs__0__Name, Storage__Inputs__0__Path, … | One or more watched input folders. Jobs are tagged with the folder's Name and resolved Profile. See below for validation rules. |
Storage:Inputs[] — validation rules (enforced at startup)
Namemust match^[a-z0-9](?:[a-z0-9-]{0,38}[a-z0-9])?$and be unique across the list — lowercase letters, digits, and internal hyphens only, 1–40 characters, starting and ending with an alphanumeric. Names appear in URL query strings, metric labels, and the dashboard UI.Pathmust be non-empty and resolve to a directory that is not the same as and not a sub-/super-directory of any other entry's path. Overlapping folders would produce double-enqueues and ambiguous attribution.- Soft cap: 16 entries. Higher counts inflate metric cardinality and the Input page beyond useful density.
- When
Storage:Inputsis omitted entirely, the service creates one folder nameddefaultat{Storage:Root}/input.
Storage:Inputs[].IgnoredExtensions / Storage:Inputs[].IgnoredPrefixes (per-folder)
Optional arrays. The effective ignore list is the union of the global
WatchedFolder:IgnoredExtensions / WatchedFolder:IgnoredPrefixes baseline and the per-folder
additions. Per-folder lists add to the baseline; they cannot un-ignore something the global list
already filters. Example: with the default baseline (.tmp, .part, .crdownload, .swp), a
folder declaring IgnoredExtensions: [".bak"] filters .bak and .tmp etc.
Example: two folders, one with an extra ignore rule
"Storage": {
"Root": "/var/lib/bulksigner",
"Inputs": [
{ "Name": "default", "Path": "/var/lib/bulksigner/input" },
{
"Name": "legal",
"Path": "/mnt/legal/incoming",
"IgnoredExtensions": [".bak"]
}
]
}
Pipeline
| Key | Type | Default | Env override | Notes |
|---|---|---|---|---|
Pipeline:PollIntervalSeconds | int | 2 | Pipeline__PollIntervalSeconds | How often the worker polls the queue when idle. Lower = faster pickup, more SQLite reads. Bounds: 1–3600. |
Pipeline:MaxConcurrency | int | 1 | Pipeline__MaxConcurrency | Upper bound on concurrent in-flight jobs. Default 1 is sequential. Increase for throughput on PFX-backed deployments. Bounds: 1–32. PKCS#11 / WindowsStore: keep at 1 unless the token / CSP allows concurrent sessions — see Certificates. |
WatchedFolder
The stability detector guards against picking up a file that is still being written — the watcher
waits until size and last-write-time stay identical across StabilityRequiredSamples consecutive
polls.
| Key | Type | Default | Env override | Notes |
|---|---|---|---|---|
WatchedFolder:StabilityPollIntervalMs | int | 500 | WatchedFolder__StabilityPollIntervalMs | Interval between stability checks. Bounds: 50–10000. |
WatchedFolder:StabilityRequiredSamples | int | 3 | WatchedFolder__StabilityRequiredSamples | Consecutive identical samples needed before enqueue. Bounds: 1–100. |
WatchedFolder:StabilityTimeoutSeconds | int | 60 | WatchedFolder__StabilityTimeoutSeconds | Maximum wait before giving up on a never-stable file. Bounds: 1–3600. |
WatchedFolder:IgnoredExtensions | array | [".tmp", ".part", ".crdownload", ".swp"] | n/a (use config) | File extensions the watcher ignores entirely. |
WatchedFolder:IgnoredPrefixes | array | [".", "~$"] | n/a (use config) | File-name prefixes the watcher ignores (dotfiles, Office lock files). |
Upload
| Key | Type | Default | Env override | Notes |
|---|---|---|---|---|
Upload:MaxBytes | long | 104857600 (100 MiB) | Upload__MaxBytes | Hard cap on the POST /api/files request body. Raise for scan-heavy PDFs. Minimum 1024. |
Dashboard
| Key | Type | Default | Env override | Notes |
|---|---|---|---|---|
Dashboard:PollIntervalSeconds | int | 5 | Dashboard__PollIntervalSeconds | Server-side refresh tick for live dashboard pages. Bounds: 1–60. |
LogViewer
Backs the in-memory recent-exception store and the /logs dashboard page. All values are read once at
startup — change them and restart.
| Key | Type | Default | Env override | Notes |
|---|---|---|---|---|
LogViewer:Enabled | bool | true | LogViewer__Enabled | Master switch. When false the in-memory sink is not wired, the /logs page shows a disabled notice, and the nav link is hidden. |
LogViewer:MaxEntries | int | 20 | LogViewer__MaxEntries | Size of the bounded in-memory buffer and the ceiling on rendered entries. Oldest entries are evicted past this limit. Bounds: 1–1000. |
LogViewer:RefreshIntervalSeconds | int | 5 | LogViewer__RefreshIntervalSeconds | Automatic refresh tick for the /logs page. Bounds: 1–60. The page also has a manual refresh button. |
LogViewer:Levels | string[] | ["Error","Fatal"] | LogViewer__Levels__0, … | Log levels the store captures (case-insensitive). Valid names: Verbose, Debug, Information, Warning, Error, Fatal. Empty or unknown names fail startup. Logging:File:MinimumLevel still applies first — widening this below that minimum captures nothing, because those events never reach the sink. |
See Dashboard.
Metrics
| Key | Type | Default | Env override | Notes |
|---|---|---|---|---|
Metrics:RequireApiKey | bool | true | Metrics__RequireApiKey | When true, /api/metrics requires API-key or cookie auth. Set false only if your Prometheus scraper sits inside the trust boundary and the network is locked down. |
See REST API for the full inventory of metrics instruments.
Statistics
| Key | Type | Default | Env override | Notes |
|---|---|---|---|---|
Statistics:Enabled | bool | true | Statistics__Enabled | Master switch. When false the collector is a no-op (no recording, no locking) and the dashboard panel is hidden. Statistics are held only in process memory and reset on restart — they are never written to the database. |
See Job statistics for what each number means.
Telemetry
Opt-in Azure Application Insights export. Off by default; when off, the service has no Application Insights dependency and makes no outbound calls on its behalf.
| Key | Type | Default | Env override | Notes |
|---|---|---|---|---|
Telemetry:Enabled | bool | false | Telemetry__Enabled | Master switch. When true, a connection string is required — startup fails without one. |
Telemetry:ConnectionString | string? | null | Telemetry__ConnectionString | SECRET. Application Insights connection string. Leave unset to supply it via the standard APPLICATIONINSIGHTS_CONNECTION_STRING environment variable instead. |
Telemetry:RoleName | string | Lacuna.BulkSigner | Telemetry__RoleName | Reported as the cloud_RoleName dimension, so several services sharing one resource stay distinguishable. |
See Telemetry for what is collected and the KQL queries to read it.
RateLimiting
Per-IP fixed-window policies.
| Key | Type | Default | Env override | Notes |
|---|---|---|---|---|
RateLimiting:Enabled | bool | true | RateLimiting__Enabled | Master switch. Disable for closed-network installs. |
RateLimiting:Upload:PermitsPerWindow | int | 30 | RateLimiting__Upload__PermitsPerWindow | Requests allowed per window for POST /api/files. |
RateLimiting:Upload:WindowSeconds | int | 60 | RateLimiting__Upload__WindowSeconds | Window length for the upload policy. |
RateLimiting:Upload:QueueLimit | int | 0 | RateLimiting__Upload__QueueLimit | How many over-limit requests wait vs. being rejected immediately. 0 = reject immediately. |
RateLimiting:Actions:PermitsPerWindow | int | 60 | RateLimiting__Actions__PermitsPerWindow | Requests allowed per window for action endpoints (retry, cancel, rescan, cleanup, pause, resume). |
RateLimiting:Actions:WindowSeconds | int | 60 | RateLimiting__Actions__WindowSeconds | Window length for the actions policy. |
RateLimiting:Actions:QueueLimit | int | 0 | RateLimiting__Actions__QueueLimit | Queue depth for the actions policy. |
Rate-limited responses carry code = "rate-limited" in the error envelope.
Hosting
| Key | Type | Default | Env override | Notes |
|---|---|---|---|---|
Hosting:RequireHttps | bool | false | Hosting__RequireHttps | Gates the in-process HTTPS redirect. false (default) for service and Docker installs that terminate TLS at a reverse proxy. Surfaced in the ready-summary banner as https redirect = on/off. |
AllowedHosts
| Key | Type | Default | Env override | Notes |
|---|---|---|---|---|
AllowedHosts | string | * | AllowedHosts | Standard ASP.NET Core host filtering. Override to a comma-separated list if the install is reverse-proxied with a fixed external host name. |
Environment variables that have no JSON counterpart
| Variable | Purpose |
|---|---|
BULK_SIGNER_CONFIG_DIR | Tells the binary where the production config lives when the binary is in a read-only install location. Set by the install scripts. |
BULK_SIGNER_PKCS11_PIN | The HSM/token PIN — read at the env-var name configured by Signing:Certificate:Pkcs11:PinEnvVar. |
BULK_SIGNER_ENCRYPTION_PASSWORD | PBKDF2 password — read at the env-var name configured by Encryption:PasswordEnvVar. |
APPLICATIONINSIGHTS_CONNECTION_STRING | Standard Azure Monitor variable. Read directly by the exporter and honoured by the startup validator, so Telemetry:ConnectionString can be left unset. See Telemetry. |
ASPNETCORE_ENVIRONMENT | Standard ASP.NET Core environment name (Development, Production). The install scripts set Production. |
ASPNETCORE_URLS | Standard. The install scripts set http://0.0.0.0:8080. |
ASPNETCORE_CONTENTROOT | Standard. The Windows install sets it to C:\ProgramData\Lacuna\BulkSigner so file-path resolution lands on operator-writable disk. |
Verifying configuration at runtime
The ready-summary banner printed on startup lists the most decision-critical settings (host mode, environment, https redirect, content root, storage root, license fingerprint, cert source, signing policy, encryption status, poll interval, pipeline mode). A mistyped key surfaces there as a default value rather than the value you intended.
/api/ready returns a JSON body describing each probe (DB, input folder, license). A 503 with a
body listing the failed probe is the fast feedback loop for config mistakes — see
Troubleshooting.
Next: Certificates — choosing and configuring a certificate source. Previous: Installation.