Configuration
HostCraft reads a single configuration file at /etc/hostcraft/config.yaml. The installer writes a working copy during hostcraftd init. Most operators edit only a handful of fields after that. This page is the complete reference for every supported field.
On-disk layout
Section titled “On-disk layout”| Path | Contents |
|---|---|
/etc/hostcraft/config.yaml |
Daemon configuration |
/etc/hostcraft/database.env |
Mode 0600. MariaDB DSN for hostcraft_admin and PowerDNS credentials. |
/etc/hostcraft/crowdsec.env |
Mode 0600. CrowdSec local API URL and bouncer key. |
/etc/hostcraft/update/cosign.pub |
Release signing public key (ECDSA P-256 PEM) the daemon verifies update manifests with. Without it in-panel updates are disabled. |
/etc/hostcraft/security/waf/owasp-crs/ |
OWASP Core Rule Set bundled at install time. |
/etc/hostcraft/nftables/loopback-guard.nft |
Loopback guard ruleset (table inet hostcraft_loopguard), rendered and loaded by the daemon. See Security. |
/etc/hostcraft/cluster/ |
mTLS CA, node certificates, and cluster state (cluster mode only). |
/etc/default/hostcraftd |
EnvironmentFile sourced by the systemd unit before start. |
/var/lib/hostcraft/state.db |
SQLite panel state (solo mode). |
/var/lib/hostcraft/snapshots/ |
Pre-update snapshots: <from>-to-<to>-<time>/db/*.db and etc/ (copy of /etc/hostcraft). |
/opt/hostcraft/bin/ |
Releases (v<version>/hostcraftd) and the current, stable, previous links. |
/var/lib/hostcraft/configs/<site>/<timestamp>.tar.zst |
Per-site config history for granular rollback. |
/var/lib/hostcraft/quarantine/<account>/<timestamp>/ |
Malware quarantine directory. |
/var/log/hostcraft/ |
Daemon log mirror (journalctl is canonical). |
/opt/hostcraft/bin/v<X.Y.Z>/hostcraftd |
Per-version binary. |
/opt/hostcraft/bin/current |
Symlink to the active version. |
/opt/hostcraft/bin/previous |
Symlink to the prior version (rollback target). |
config.yaml reference
Section titled “config.yaml reference”Below is an annotated full config with every supported field. Fields that are optional and have a sensible default are marked with their default value. Fields with no default are required or conditionally required as noted.
# -----------------------------------------------------------------------# Server# -----------------------------------------------------------------------server: # The address the daemon's REST/WebSocket API listens on. # Caddy terminates TLS in front; keep this on loopback. # Default: "127.0.0.1:7700" listen_addr: "127.0.0.1:7700"
# Unix socket serving the gRPC API (every service in # daemon/proto/hostcraft/v1). Mode 0660. Local root is identified by # SO_PEERCRED; any other caller must send an API token as # "authorization: Bearer hcp_...". Empty disables the socket. # Default: "/run/hostcraft/hostcraftd.sock" grpc_socket: "/run/hostcraft/hostcraftd.sock"
# Group owning the gRPC socket. Add operators who script against the # API to this group (they still need an API token). # Default: "hostcraft-admin" grpc_socket_group: "hostcraft-admin"
# Optional additional gRPC listener on TCP, for tools that cannot use a # Unix socket. Loopback addresses only (127.0.0.1, ::1, localhost); the # daemon refuses to start on any other address. No TLS; every caller # needs an API token. # Default: "" (off) grpc_addr: ""
# The public FQDN of this panel. Used in email links, ACME certificate # requests, and HELO banners. Required. panel_hostname: "panel.example.com"
# Runtime mode: "solo", "panel", or "agent". # "solo" is the default and runs everything on one box. # "panel" enables cluster management features. # "agent" runs managed services only and takes orders from a remote panel. # Default: "solo" mode: "solo"
# This server's public addresses, comma separated, as the installer # detected them. Caddy only requests a public certificate when the # panel hostname resolves to one of them. public_ips: "203.0.113.10"
# 1:1 NAT pairs, comma separated: private address on the NIC = public # address the router maps to it. Services (nginx, postfix) bind the # private side; DNS records, SPF and the panel publish the public side. # Empty on a server whose NIC carries its public address. The # installer fills it in when it detects NAT (see "Servers behind 1:1 # NAT" below). # Default: "" (no NAT) nat_map: ""
# Name of the session cookie set by the panel. # Change this if you run two panels on the same domain (unusual). # Default: "hc_session" cookie_name: "hc_session"
# -----------------------------------------------------------------------# State (panel database)# -----------------------------------------------------------------------state: # DSN for the panel's own database. # SQLite (default for solo): # "file:/var/lib/hostcraft/state.db?_journal=WAL&_busy_timeout=5000" # PostgreSQL (required for cluster panel mode): # "postgres://hostcraft:pass@localhost:5432/hostcraft?sslmode=require" # Default: SQLite path above dsn: "file:/var/lib/hostcraft/state.db?_journal=WAL&_busy_timeout=5000"
# -----------------------------------------------------------------------# Auth# -----------------------------------------------------------------------auth: # Secret used to HMAC-sign session cookies and API tokens. # REQUIRED in production. Minimum 32 bytes of random data. # Generate with: openssl rand -hex 32 # No default. The daemon refuses to start if this is empty in non-dev mode. session_secret: ""
# How long a fully active session lasts before the user must re-authenticate. # Default: "12h" session_lifetime: "12h"
# How long an idle session (no requests) persists before expiry. # Default: "2h" idle_timeout: "2h"
# License key file (ES256 JWT). Install or replace it with # `hostcraftd license install --file <path>` or the panel's License page. # With no license the server runs on the permanent free tier # (3 accounts, 10 sites). Default: /etc/hostcraft/license.jwt license_file: /etc/hostcraft/license.jwt
# Optional ADDITIONAL Ed25519 or ECDSA P-256 public key (PEM path) that is added to # the verification keys embedded in the binary. It can never replace or # disable the embedded keys. The old name license_public_key is accepted. # Default: empty license_dev_public_key: ""
# Optional lifetime cap for that additional key (Go duration, e.g. 2400h). # When set, its tokens must keep exp minus iat within the cap, may not be # issued in the future and may carry at most 30 grace days, the same rules # as the online signing key. Default: empty (uncapped) license_dev_public_key_max_lifetime: ""
# Expected "aud" claim. Licenses are issued wildcard-bound ("*"), so the # default leaves this empty, which accepts any audience. license_audience: ""
# Renewal check endpoint (https only). Empty disables renewal checks for # air-gapped servers. Default: https://updates.hostcraft.io/license/check license_refresh_url: https://updates.hostcraft.io/license/check license_refresh_interval: 6h
# enforce (default) or warn_only. warn_only logs and audits every # would-be license block without blocking. license_enforcement: enforce
# -----------------------------------------------------------------------# Update# -----------------------------------------------------------------------update: # Update channel: "stable", "beta", or "dev". # Default: "stable" channel: "stable"
# Base URL for the update server. # Default: "https://updates.hostcraft.io" server_url: "https://updates.hostcraft.io"
# Release directory with v<version>/ and the current, stable and # previous links. Default: "/opt/hostcraft/bin" binary_install_dir: "/opt/hostcraft/bin"
# Release signing public key. Default: "/etc/hostcraft/update/cosign.pub" cosign_pub_key_path: "/etc/hostcraft/update/cosign.pub"
# -----------------------------------------------------------------------# Telemetry# -----------------------------------------------------------------------telemetry: # Whether to send anonymous usage telemetry to JCR. # Telemetry never includes customer data; it covers feature usage counters # and daemon performance histograms. # Required for JCR-managed installs. Opt-in for self-hosted licensees. # Default: true enabled: true
# -----------------------------------------------------------------------# Notify (outbound email from the panel itself)# -----------------------------------------------------------------------notify: # SMTP host for panel notification emails (password resets, alerts, etc.) # Default: "mail.smtp2go.com" smtp_host: "mail.smtp2go.com"
# Default: 587 smtp_port: 587
# SMTP username and password for the transactional mail account. # Required when smtp_host is set. smtp_username: "" smtp_password: ""
# From address for panel notification emails. # Default: "noreply@<panel_hostname>" smtp_from: ""
# Whether to use STARTTLS. Set to false only for local testing. # Default: true smtp_starttls: true
# -----------------------------------------------------------------------# Paths# -----------------------------------------------------------------------paths: # Root directory where account home directories are created. # XFS mount recommended for project quota support. # Default: "/home" home_dir_root: "/home"
# Directory where nginx vhost configs are written. # Default: "/etc/nginx/sites-enabled" nginx_dir: "/etc/nginx/sites-enabled"
# Directory prefix for PHP-FPM pool configs. # Actual files land at <fpm_dir>/php<ver>-fpm.d/<account>.conf # Default: "/etc/php" fpm_dir: "/etc/php"
# Directory where systemd unit files for per-site services are written. # Default: "/etc/systemd/system" unit_dir: "/etc/systemd/system"
# Directory for FPM Unix sockets. # Default: "/run/php" socket_dir: "/run/php"
# Directory for panel state (SQLite DB, snapshots, version binaries). # Default: "/var/lib/hostcraft" state_dir: "/var/lib/hostcraft"
# -----------------------------------------------------------------------# Database (customer databases, not the panel state DB)# -----------------------------------------------------------------------database: # Database engine for customer databases. # "mariadb" (default) or "mysql" engine: "mariadb"
# DSN for the admin connection used to create/drop databases and users. # Loaded from /etc/hostcraft/database.env by default if this field is empty. # Default: read from database.env admin_dsn: ""
# Directory where per-database config snippets are written. # Default: "/etc/hostcraft/db" config_dir: "/etc/hostcraft/db"
# Base URL shown in the Adminer SSO link. # Default: "https://<panel_hostname>/db/manager" panel_url: ""
# -----------------------------------------------------------------------# DNS (PowerDNS)# -----------------------------------------------------------------------dns: # DSN for the PowerDNS gmysql backend. # Loaded from /etc/hostcraft/database.env if empty. # Default: read from database.env pdns_dsn: ""
# Default nameservers written to newly created zones. # Required. Usually two nameservers. default_nameservers: - "ns1.example.com." - "ns2.example.com."
# Default A record IP for new zones (the server's PUBLIC IPv4). Behind # 1:1 NAT this is the public address, never the private one on the NIC. # A private value is replaced at boot by its server.nat_map public side # (or the first public IPv4 in server.public_ips) and `hostcraftd # doctor` flags it. # Required. default_ip: ""
# Whether to enable DNSSEC automatically on new zones. # Default: false auto_enable_dnssec: false
# -----------------------------------------------------------------------# SSL (acme.sh)# -----------------------------------------------------------------------ssl: # Path to the acme.sh binary. # Default: "/opt/acme.sh/acme.sh" binary_path: "/opt/acme.sh/acme.sh"
# acme.sh home directory (where account keys and cert metadata are stored). # Default: "/var/lib/hostcraft/acme-home" home_dir: "/var/lib/hostcraft/acme-home"
# Directory where issued certificates are installed. # Default: "/etc/hostcraft/ssl/certs" certs_dir: "/etc/hostcraft/ssl/certs"
# Document root used for HTTP-01 ACME challenges. Every site vhost maps # /.well-known/acme-challenge/ on port 80 to this directory. # Default: "/var/lib/hostcraft-acme/webroot" webroot_dir: "/var/lib/hostcraft-acme/webroot"
# CA used when a certificate does not name one. Each certificate records # its own CA (Let's Encrypt, ZeroSSL, Buypass, Google). # Default: "letsencrypt" default_ca: "letsencrypt"
# Optional ACME directory URL that replaces the public CA for every # issuance and renewal, for an internal ACME CA (step-ca, Boulder). # Must be https. Default: "" (use the certificate's CA) acme_server: ""
# Optional PEM file of extra roots acme.sh trusts when talking to # acme_server, for a CA whose HTTPS certificate is not publicly trusted. # Default: "" ca_bundle: ""
# Email address registered with the ACME CA account. Optional. # Default: "" account_email: ""
# AutoSSL: issue a certificate (HTTP-01) automatically when a site is # created, once its vhost is live, and install it into that site. # Skipped when the domain already has an uploaded or restored certificate. # Default: true auto_issue_on_domain_created: true
# -----------------------------------------------------------------------# Redis# -----------------------------------------------------------------------redis: # Redis address used by the panel object cache and Rspamd. # Default: "127.0.0.1:6379" address: "127.0.0.1:6379"
# Admin password for Redis AUTH. Empty means no auth (loopback-only default). # Default: "" admin_password: ""
# Set to true to skip binding Redis to a network interface. # Useful when Redis is managed externally. # Default: false skip_network: false
# -----------------------------------------------------------------------# Cluster# -----------------------------------------------------------------------cluster: # Cluster mode: "solo", "panel", or "agent". # Mirrors server.mode. Set both consistently. # Default: "solo" mode: "solo"
# Stable identifier for this node. Generated at init; do not change. node_id: ""
# Hostname other nodes use to reach this node. # Default: same as server.panel_hostname hostname: ""
# For agent mode: URL of the panel node. # Required when mode is "agent". panel_url: ""
# NATS server address for cluster fan-out. # In panel mode the embedded nats-server (always built in) listens here. # In agent mode this is the panel NATS address to connect to. # Unused in solo mode. # Default: "127.0.0.1:4222" nats_addr: "127.0.0.1:4222"
# Fingerprint of the cluster CA certificate. Pinned at join time. # Set automatically by "hostcraftd join"; do not edit manually. ca_fingerprint: ""
# Directory where the cluster CA certificate lives (panel node only). # Default: "/etc/hostcraft/cluster/ca" ca_dir: "/etc/hostcraft/cluster/ca"
# Directory where per-node certificates are stored. # Default: "/etc/hostcraft/cluster/nodes" nodes_dir: "/etc/hostcraft/cluster/nodes"
# -----------------------------------------------------------------------# WP vulnerability scanning# -----------------------------------------------------------------------wp_vuln: # API key for Patchstack. Enables Patchstack as the primary vulnerability feed. # Optional; falls back to WPScan and the NVD CVE feed without it. patchstack_api_key: ""
# API key for WPScan. Enables WPScan as a secondary feed. # Optional. wpscan_api_key: ""
# Directory for cached NVD CVE data. # Default: "/var/lib/hostcraft/nvd-cache" nvd_cache_dir: "/var/lib/hostcraft/nvd-cache"
# How often to refresh the vulnerability feeds. # Default: 24 scan_interval_hours: 24
security: # Turns off the loopback guard, the nftables rule that stops hosting # account users (login.defs UID_MIN..UID_MAX) from opening the daemon's # loopback ports (listen_addr and a loopback grpc_addr). Leave it on. # Default: false disable_loopback_guard: falseServers behind 1:1 NAT
Section titled “Servers behind 1:1 NAT”Some networks give the VM a private address on its NIC (for example 10.0.0.5) and map a public address (for example 203.0.113.10) to it on the router, one to one. HostCraft supports this: daemons bind the address that is really on the interface, and everything published to the outside world carries the public one.
| What | Uses |
|---|---|
nginx listen for dedicated IPs, postfix (inet_interfaces = all) |
The private (bound) address |
| New zone A records, records moved when a dedicated IP is assigned or released, cPanel restore rewrites, the IP shown in the panel | The public address |
What the installer does:
- Detects the public IPv4 (
server.public_ips). When that address is not on any interface and the primary address is private, it recordsserver.nat_map: <private>=<public>. - Writes the public IPv4 to
dns.default_ip, so new zones never point at the private address. - Override detection with
--public-ips=203.0.113.10 --nat-map=10.0.0.5=203.0.113.10.
Example:
server: public_ips: 203.0.113.10 nat_map: 10.0.0.5=203.0.113.10,10.0.0.6=203.0.113.11dns: default_ip: 203.0.113.10Dedicated IPs. Bind each extra private address on the server (netplan), map its public address on the router, and either add the pair to server.nat_map or set it in the panel: Admin, IP addresses, Edit note and public address. A pair in server.nat_map wins over a panel edit at the next restart or interface scan. Pool entries keep the private address as their key (nginx binds it) and publish the public one. Add the private address, not the public one: adding an address that is already some entry's public side is refused. The public address of an assigned IP cannot change; release it, edit, then assign again.
cPanel/WHM restore. Imported A records that point at the old server's IP are moved to this server's shared public IPv4 unless the IP map sends them elsewhere. An IP map target may be either side of a NAT pair; the assignment binds the private address and DNS gets the public one.
Hairpin NAT. Clients on the same private network (and the server itself) reach the public address only if the router supports NAT loopback. If it does not, use split DNS or /etc/hosts entries for internal clients. External visitors are not affected.
Check it. sudo hostcraftd doctor prints a [nat] line: public IP ... is on a local interface (no NAT), behind 1:1 NAT: DNS publishes <public>, services bind <private>, or an explanation of what to fix (a private dns.default_ip, or a public IP that is not on any interface with an empty server.nat_map).
Existing installs. Upgrading does not rewrite config.yaml. If doctor reports a private dns.default_ip, set dns.default_ip to the public address and add server.nat_map, then systemctl restart hostcraftd. Zones created before the fix keep their old A records; update them in the DNS editor.
Build tags
Section titled “Build tags”The embedded nats-server is always compiled in; no build tag is needed for clusters. One tag remains relevant to people building from source.
| Tag | Effect |
|---|---|
goaccess_real |
Enables GoAccess integration tests against a real GoAccess binary. Used in CI on machines where GoAccess is installed; not needed for production builds. |
Environment variables
Section titled “Environment variables”Edit /etc/default/hostcraftd then run systemctl restart hostcraftd. These variables are sourced by the unit before the binary starts and override the corresponding config.yaml fields.
| Variable | Default | Notes |
|---|---|---|
HOSTCRAFT_CONFIG_DIR |
/etc/hostcraft |
Config directory. |
HOSTCRAFT_STATE_DIR |
/var/lib/hostcraft |
SQLite, snapshots, version dirs. |
HOSTCRAFT_LOG_DIR |
/var/log/hostcraft |
Structured log mirror. |
HOSTCRAFT_LISTEN_ADDR |
127.0.0.1:7700 |
Panel REST/WebSocket listen address. |
HOSTCRAFT_CHANNEL |
stable |
Update channel. |
HOSTCRAFT_DEBUG |
0 |
Set to 1 for debug-level slog output. |
HOSTCRAFT_ADMIN_EMAIL |
(none) | Used by hostcraftd init for unattended bootstrap. |
HOSTCRAFT_ADMIN_PASSWORD |
(none) | Used by hostcraftd init for unattended bootstrap. |
HOSTCRAFT_UPDATES_BASE |
https://updates.hostcraft.io |
Override for air-gapped or staging update servers. |
Applying config changes
Section titled “Applying config changes”The daemon does not hot-reload config.yaml. After any edit:
sudo hostcraftd config validate # dry-run parse, exits non-zero on errorsudo systemctl restart hostcraftdsudo hostcraftd doctor # verify the running daemon is healthyNever restart the daemon during an active workflow run. The workflow engine persists checkpoints and resumes after restart, but leaving a restart until workflows are idle reduces the chance of a mid-saga interruption. Check for running workflows on the Workflows page in the panel (or GET /api/v1/workflows).