Server Administration
Launching as a Service or Application
The server can be configured to launch as a service or application and automatically detects which mode it's in.
By default the server binds to all network interfaces. To restrict it to a specific interface or loopback address, set server.hostname in the configuration file (e.g. "hostname": "127.0.0.1" to accept only local connections).
For running as a service on Linux systems, run the included install.sh script and/or refer to the htmql.service systemd service file.
For running as a service on Windows systems, run the included install.bat script.
Caching
Whether page source is held in memory is controlled by the pages.cache setting (default true). When disabled, pages are read from disk on every request.
Separately, the server can auto-rescan the pages directory for file changes (additions, removals, and edits) on an interval set by pages.refreshScanSeconds. By default, files are checked every 60 seconds. Setting it to 0 disables auto-rescanning; the cache is then only refreshed on demand or at restart.
These two settings are independent. For example, cache: true with refreshScanSeconds: 0 loads all pages into memory at startup and never auto-rescans, which suits locked-down environments where on-disk edits should not take effect until explicitly reloaded.
Logging
By default, the server logs to a file named htmql-server.log in the root directory.
The log path can be configured using the logging.file.path setting.
The server manages rotation of the log files based on the logging.file.maxSizeMB setting, which defaults to 10 MB.
When the logs reach their maximum size, they are rotated. The number of rotated files is controlled by the logging.file.maxBackups setting, which defaults to 10.
There are various boolean (true/false) options in the configuration file to turn on/off logging for different types of events.
logging.http- logs all HTTP page requests (does not include asset, lib, and file folders)logging.forms- logs the form submission values sent with HTTP requests. Values of fields whose name looks sensitive (containspassword,secret,token,apikey, etc.) are masked as[REDACTED]so credentials are never written to the log. Add more field-name substrings to redact withlogging.redactFields(e.g.["ssn", "sin", "creditcard"]); matching is case-insensitive and by substring, on top of the built-in set.logging.queries- logs all SQL queries executed by the serverlogging.transactions- logs all SQL transaction start, commit, and rollback eventslogging.skipped- logs page SQL commands that are skipped due to missing parameterslogging.caching- logs all page caching events. Errors are always logged regardless of this setting.logging.session- logs session creation and removallogging.execute- logs external executable eventslogging.tlsHandshakeErrors- logs TLS handshake errors from net/http (default off). On a public host these are dominated by IP scanners probing port 443, and with ACME by requests for hostnames not in the whitelist; they are benign, so they are dropped by default rather than logged at ERROR. Turn this on to see them (emitted at INFO) when debugging a TLS or ACME whitelist problem. Development mode enables it automatically.
If you run the server as an application (e.g. from the command line), the logs will also be output to the console.
Page-View Logging (Analytics)
When analytics.enabled is true, the server records one row per page request to a database table, so you can report on traffic (visits per page, over time) with ordinary SQL. Unlike logging.http — which writes to the log file — this writes structured rows to the database.
Only requests to the page route are logged; static assets (/asset, /lib), the favicon, and authentication endpoints are never recorded. Add analytics.ignorePaths prefixes to exclude specific pages (e.g. a health check).
The logging is performed by server middleware, not by page SQL, so no page changes are needed and there is no cross-site-request-forgery concern (a page view is a GET, and mutating on GET from a page would otherwise be flagged). The INSERT runs in a background goroutine, so logging never adds latency to the response; if an insert fails (for example, the table does not exist), a single warning is logged and further failures are suppressed.
You are responsible for creating the destination table (named by analytics.table, default page_view). The server writes these columns, which must exist:
| Column | Type | Value |
|---|---|---|
viewed_at |
text/datetime | UTC timestamp, YYYY-MM-DD HH:MM:SS |
path |
text | Request path, e.g. /docs/configuration |
method |
text | HTTP method (normally GET) |
status |
integer | HTTP response status (200, 404, ...) |
ip |
text | Resolved client IP (honors server.trustedProxies) |
user_agent |
text | User-Agent request header |
referer |
text | Referer request header |
A minimal SQLite migration:
CREATE TABLE IF NOT EXISTS page_view (
id INTEGER PRIMARY KEY AUTOINCREMENT,
viewed_at TEXT NOT NULL,
path TEXT NOT NULL,
method TEXT,
status INTEGER,
ip TEXT,
user_agent TEXT,
referer TEXT
);
The column names are fixed. Geolocation and reverse-DNS are intentionally not performed inline — a DNS or geo lookup on every request would add latency to page loads. Log the IP now and enrich later: add your own nullable columns (e.g. country, reverse_dns) and populate them with a periodic batch job that reads unenriched rows, resolves them, and writes the results back. Note that a client IP is personal data in some jurisdictions; disclose collection where required.
Development Mode
When development mode is enabled, any error messages displayed to the user about page rendering (e.g. template parsing issues, critical SQL command failures) will include all the same technical details found in the logging messages.
If development mode is disabled, the technical details are logged but not displayed to the user.
Development mode is enabled by setting the development configuration option to true.
Development mode also enables all types of logging messages in the log file.
Development mode also disables page source caching: pages are read from disk on every request so edits are picked up immediately, regardless of pages.cache. (Note that the menu/routing metadata is still only refreshed by the background scanner, which runs when refreshScanSeconds > 0.)
Admin Management Page
The server provides a built-in administration page for operators. It is served at admin.route (default /admin) and shows server status, live sessions, page-cache statistics, and the running configuration, with actions to reload the page cache and end sessions.
Enabling it. The feature is off unless auth.adminRole is set. There is no way to authorize access without an admin role, so a blank adminRole disables the route entirely (requests fall through to normal page resolution). When enabled, an "Admin" entry appears in the navigation menu for admins only.
Authorization. The route is gated by auth.adminRole: only an authenticated user holding that role may access it. This is enforced at the route itself - independent of any page policy - and re-checked inside each action, so it is not weakened by how the page is written. Unlike normal pages, the admin route is never public, even when auth.type is none (in that mode no one holds the role, so it stays inaccessible). The admin route also takes precedence over any page at the same URL; a conflicting pages/ file is logged as an error and never served.
Built-in actions (POST only, all logged):
POST {admin.route}/reload- reloads the page cache (adds new pages, drops deleted ones, re-parses changed ones, and rebuilds the menu). This is the on-demand counterpart topages.refreshScanSeconds, and the intended refresh mechanism when auto-rescanning is disabled (refreshScanSeconds: 0).POST {admin.route}/kill- ends a session, identified by an opaque handle (the raw session ID is never exposed). Ending your own session logs you out.
Customizing the page. admin.page (default admin.htmql, under the system folder) is an ordinary HTMQL page you can edit or replace - like 404.html. It must begin with the standard {{/* ... */}} header (so you can add your own SQL aliases or custom POST handlers), and the server injects ready-to-use data automatically - page-cache stats, the session list (redacted), server/uptime info, and the redacted configuration. A custom subpath {admin.route}/<name> is routed to the page with @action set to <name> so your own header commands can handle it. The Admin menu entry's label, icon, and position default in code but can be overridden with the normal #/## menu directives in the page header. See the shipped admin.htmql for the full list of injected data and examples.
Security note. Secret-bearing configuration values (passwords, client secrets, access keys, etc.) are masked before display, and a fixed-width mask hides their length. The session list shows an opaque handle rather than the real session ID.