HTTPS and Certificates
The panel can serve HTTPS on its own, without a reverse proxy. A certificate can be taken from files, set directly in the configuration, or obtained automatically via Let’s Encrypt. The quickest way to start is a self-signed certificate issued by one gameapctl command — see Setup via gameapctl.
Settings are defined in config.env — /etc/gameap/config.env on Linux
(~/.config/gameap/config.env for a rootless installation),
C:\gameap\web\config.env on Windows. After a change, a restart is required:
gameapctl panel restart.
Certificate sources
The panel picks the source itself, in this order:
- ACME — if
ACME_ENABLED=true,ACME_EMAIL, andACME_DOMAINSare set. - Files — if
TLS_CERT_FILEandTLS_KEY_FILEare set. - Values in the configuration — if
TLS_CERTandTLS_KEYare set. - No certificate — the panel serves HTTP only.
It is pairs that are checked: a TLS_CERT_FILE alone, without TLS_KEY_FILE, does not count
as a configured source and is silently ignored.
gameapctl follows the same precedence: gameapctl panel https enable refuses to run while ACME is
configured, because the panel would ignore the certificate on disk anyway, and
gameapctl panel https disable switches both off.
HTTPS listens on the port from HTTPS_PORT (443 by default) and only when a certificate is
available. HTTP on HTTP_PORT is always on. The panel’s own fallback for HTTP_PORT is 8025,
but gameapctl panel install writes HTTP_PORT=80 in system scope and HTTP_PORT=8025 in user
scope — check config.env to see which port your installation actually uses.
The panel certificate is unrelated to the gRPC certificates the panel uses to talk to daemons. Those are issued automatically by an internal certificate authority; ACME does not apply to them. See GRPC API for details.
Setup via gameapctl
The panel terminates TLS itself, so there is no web server to configure. One command issues a
self-signed certificate, points config.env at it, and restarts the panel:
gameapctl panel https enable
gameapctl panel https status
gameapctl panel https disable
The certificate covers the configured HTTP_HOST, the machine’s host name, every address of its
network interfaces, and loopback (localhost, 127.0.0.1, ::1); it is valid for 825 days.
A rerun keeps a certificate that still covers every requested name and is not about to expire, so
the browser exception you have already accepted survives. Being self-signed, the certificate is
not trusted by browsers until it is added to the trust store of each machine that opens the panel;
copy it from the path status prints.
A certificate you already have is used instead with --cert and --key. The files are left
where they are — point the flags at the live files of an external ACME client, and the panel picks
up every renewal on restart.
| Flag | Purpose |
|---|---|
--cert |
Path to a PEM certificate to use instead of a self-signed one; requires --key |
--key |
Path to the private key of --cert |
--domain |
Domain the certificate has to cover. Repeatable; replaces the detected names |
--ip |
IP address the certificate has to cover. Repeatable; replaces the detected addresses |
--port |
HTTPS port: 443, or 8443 with --scope=user |
--days |
Validity of the self-signed certificate in days, 825 by default |
--force-https |
Redirect HTTP requests to HTTPS (TLS_FORCE_HTTPS). Left as configured when absent |
--force |
Reissue the self-signed certificate even when the current one still fits |
--scope |
Installation scope, system or user; detected from the install state by default |
HTTP keeps answering on its own port. Turn on --force-https only once you are sure the HTTPS
port is reachable — otherwise you lose access to the panel from anywhere the redirect target is
blocked.
Both listeners are served by one process, and the panel exits when it cannot load the
certificate it is configured with. enable therefore verifies that the panel comes back up
serving exactly the certificate it just wrote, and restores the previous config.env and
restarts the panel when it does not.
disable removes the TLS variables from config.env (and the ACME_* variables, when ACME is
what is in effect) and restarts the panel on plain HTTP. disable --purge also deletes the
certificate and key gameapctl issued; a certificate supplied via --cert is kept.
status prints the scope, the configuration path, the HTTP and HTTPS addresses, the certificate
source, the certificate actually served, and its expiry date.
Where gameapctl stores the certificate it issues:
| Scope | Certificate | Private key | Default port |
|---|---|---|---|
| system | /etc/gameap/certs/panel.crt |
/etc/gameap/certs/panel.key |
443 |
| user | ~/.config/gameap/certs/panel.crt |
~/.config/gameap/certs/panel.key |
8443 |
| Windows | C:\gameap\web\certs\panel.crt |
C:\gameap\web\certs\panel.key |
443 |
Certificate from files
TLS_CERT_FILE=/etc/gameap/certs/panel.crt
TLS_KEY_FILE=/etc/gameap/certs/panel.key
HTTPS_PORT=443
These are the paths gameapctl itself uses in system scope; ~/.config/gameap/certs/ in user scope
and C:\gameap\web\certs on Windows. Rather than editing config.env by hand, apply a
certificate of your own with gameapctl panel https enable --cert=<path> --key=<path> — the
command writes the variables and then verifies that the panel came back up serving that
certificate, rolling the configuration back if it did not. Without that check, a bad pair takes
the panel down: it exits when it cannot load a configured certificate.
The certificate file must contain the full chain: the certificate itself, then the intermediates. Without the intermediates, some clients will not be able to verify the signature.
The files are read at startup. After replacing the certificate, restart the panel — it does not watch the files for changes on its own.
Certificate directly in the configuration
Convenient when the configuration is rolled out by a secret management system and extra files on disk are undesirable.
TLS_CERT=LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t...
TLS_KEY=LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0t...
Both plain PEM and base64-encoded PEM are accepted — the panel detects the format itself. Since
the config.env format does not support multi-line values, in practice base64 is used:
base64 -w0 panel.crt
base64 -w0 panel.key
Let’s Encrypt
The panel has a built-in ACME client: the certificate is issued and renewed without external tools like certbot.
| Variable | Default | Purpose |
|---|---|---|
ACME_ENABLED |
false |
Enables automatic issuance |
ACME_EMAIL |
"" |
Address for expiry notifications. Required |
ACME_DOMAINS |
"" |
Comma-separated domains. Required |
ACME_CHALLENGE_TYPE |
http-01 |
Challenge type: http-01 or dns-01 |
ACME_DNS_PROVIDER |
"" |
DNS provider, dns-01 only |
ACME_DIRECTORY_URL |
production ACME | ACME directory URL |
ACME_RENEWAL_THRESHOLD |
720h |
How long before expiry to renew. 30 days by default |
ACME_RENEWAL_CHECK_INTERVAL |
12h |
How often to check the expiry date |
ACME_PROPAGATION_TIMEOUT |
180s |
How long to wait for DNS record propagation with dns-01 |
ACME_STORAGE_PATH |
acme |
Directory for certificates and the ACME account key |
ACME is enabled only when
ACME_ENABLED=true,ACME_EMAIL, andACME_DOMAINSare all set, and fordns-01alsoACME_DNS_PROVIDER. If anything is missing, the panel starts without ACME and without an error. Check the outcome via the status in the admin area.
http-01 challenge
The default method. It requires nothing except the panel being reachable from the internet.
ACME_ENABLED=true
ACME_EMAIL=admin@example.com
ACME_DOMAINS=panel.example.com
ACME_CHALLENGE_TYPE=http-01
TLS_FORCE_HTTPS=true
What is needed:
- the domains from
ACME_DOMAINSmust resolve to this server’s address; - requests to port 80 must reach the panel — that is the port the certificate authority connects to;
- the panel serves the
/.well-known/acme-challenge/path itself, on its HTTP port.
The certificate authority always connects to port 80. An installation made with
gameapctl panel installin system scope already listens there (HTTP_PORT=80). If the panel is on another port — the built-in fallback is8025, and the installer picks the first free port of8025,8026, … when 80 is taken — checkHTTP_PORTinconfig.envand either setHTTP_PORT=80or forward port 80 to the panel port using system tools:iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8025Substitute the
HTTP_PORTvalue fromconfig.envfor8025in--to-port.Such a rule only lives until the next reboot. Persist it with the firewall tooling of the distribution — the one that restores rules at boot — or, instead of forwarding, put a reverse proxy listening on port 80 in front of the panel.
Port 80 is needed not only for the first issuance: the challenge is repeated on every renewal, so it must not be closed afterwards.
In a rootless installation the panel
cannot bind port 80 itself — an unprivileged process is not allowed to. The challenge still
succeeds when port 80 is forwarded to the panel’s HTTP port or the panel is fronted by a reverse
proxy: the panel serves /.well-known/acme-challenge/ on its own HTTP port. Without such
forwarding, use dns-01 there.
The http-01 method does not issue wildcard certificates (*.example.com) — those require
dns-01.
dns-01 challenge
Needed when the panel is not reachable from the internet on port 80 or a wildcard certificate is required.
Of the built-in providers, only Cloudflare is supported. Others are added via plugins: in
that case ACME_DNS_PROVIDER is set to <plugin-id>:<provider-name>.
ACME_ENABLED=true
ACME_EMAIL=admin@example.com
ACME_DOMAINS=panel.example.com,*.example.com
ACME_CHALLENGE_TYPE=dns-01
ACME_DNS_PROVIDER=cloudflare
CLOUDFLARE_DNS_API_TOKEN=token_from_the_cloudflare_dashboard
The token is created in Cloudflare with the Zone → DNS → Edit permission for the zone in
question. Besides CLOUDFLARE_DNS_API_TOKEN, the variables CF_DNS_API_TOKEN,
CLOUDFLARE_API_TOKEN, and CF_API_TOKEN are accepted, as well as the legacy
“global key + email” pair: CLOUDFLARE_API_KEY together with CLOUDFLARE_EMAIL. A token with
restricted permissions is preferable.
If DNS records propagate slowly, increase ACME_PROPAGATION_TIMEOUT.
Let’s Encrypt via gameapctl
Instead of editing config.env by hand, you can use the wizard:
gameapctl panel https letsencrypt setup
It asks for the domains, the email address, and the challenge type, writes the settings to
config.env, and restarts the panel.
The command used to be
gameapctl panel letsencrypt. That form still works as a deprecated alias, but it is hidden from--help; usegameapctl panel https letsencrypt.
The same call without questions:
gameapctl panel https letsencrypt setup --non-interactive \
--domains=panel.example.com \
--email=admin@example.com \
--challenge=http-01
Useful flags:
| Flag | Purpose |
|---|---|
--challenge |
http-01 or dns-01 |
--domains |
Comma-separated domains |
--email |
ACME account address |
--dns-provider |
DNS provider for dns-01 |
--env |
Extra KEY=VALUE lines for config.env — for DNS credentials |
--staging |
Let’s Encrypt staging directory |
--non-interactive |
Ask no questions; fail with an error when parameters are missing |
--scope |
Installation scope, system or user; detected from the install state by default |
Disabling:
gameapctl panel https letsencrypt disable
The command removes the ACME_* variables from config.env and restarts the panel. The
--purge-certs flag is declared but not implemented yet — issued certificates remain on disk.
Debugging issuance
The production Let’s Encrypt directory has strict limits on the number of attempts per domain, and it is easy to exhaust them while setting things up. Until your setup works, use the staging directory:
ACME_DIRECTORY_URL=https://acme-staging-v02.api.letsencrypt.org/directory
Browsers treat its certificates as untrusted, but the limits are far more relaxed. Once issuance
succeeds, remove this variable, delete the contents of the ACME_STORAGE_PATH directory, and
restart the panel to obtain a production certificate.
Renewal
The panel checks the expiry date every ACME_RENEWAL_CHECK_INTERVAL (every 12 hours by default)
and renews the certificate when less than ACME_RENEWAL_THRESHOLD remains until expiry (30 days
by default). No separate scheduler or cron job is needed.
Certificates, the ACME account key, and housekeeping data are stored in the ACME_STORAGE_PATH
directory inside the panel’s file storage. With FILES_DRIVER=s3 they end up in S3 — this is
what lets several panel instances share one certificate.
Certificate status
The current status is available to an administrator at GET /api/admin/letsencrypt/status:
{
"enabled": true,
"state": "active",
"challenge_type": "http-01",
"domains": ["panel.example.com"],
"not_after": "2026-10-30T12:00:00Z",
"last_renewal_at": "2026-08-01T12:00:00Z",
"next_renewal_check_at": "2026-08-02T00:00:00Z"
}
Possible state values:
| Value | Meaning |
|---|---|
disabled |
ACME is disabled |
pending |
The certificate has not been issued yet |
active |
The certificate is issued and valid |
renewing |
Renewal is in progress |
failed |
The last attempt failed; the reason is in last_error |
Redirecting to HTTPS
TLS_FORCE_HTTPS=true
All HTTP requests get a 301 redirect, except /.well-known/acme-challenge/ — otherwise the
http-01 challenge would stop working.
The same variable affects two other mechanisms: the HSTS header starts being sent even when TLS
terminates at a reverse proxy, and the CORS origin is computed with the https scheme.
Panel behind a reverse proxy
If TLS terminates at nginx, Traefik, or another proxy, there is no need to configure certificates
in the panel — leave ACME_ENABLED=false and do not set TLS_*. The panel will serve HTTP on
HTTP_PORT, and the proxy will handle HTTPS.
What matters in this setup:
- The proxy must pass the
X-Forwarded-Proto: httpsheader, otherwise the panel will not know the connection is secure and will not send HSTS. - The proxy must overwrite the
X-Forwarded-Protoheader and the header fromAUDIT_CLIENT_IP_HEADER, not append to them: the panel trusts them without verifying the sender. - Port 31718 usually does not go through the proxy — daemons must connect to the panel
directly. Set
GRPC_EXTERNAL_HOSTto the address at which the panel is reachable by daemons. - If the public address differs from
HTTP_HOST, list it inHTTP_ALLOWED_ORIGINS.
Common problems
| Symptom | Cause |
|---|---|
| The panel started, but HTTPS is not listening | A variable pair is not set in full, or one of the required ACME_* variables is missing |
state: failed with http-01 |
Port 80 is not reachable from outside, the domain does not resolve to this server, or another service intercepts it |
state: failed with dns-01 |
The DNS token lacks permissions, or the record did not propagate in time — increase ACME_PROPAGATION_TIMEOUT |
| Issuance stopped working after several attempts | The production Let’s Encrypt rate limit is exhausted. Switch to the staging directory and finish the setup there |
| The browser complains about the chain | TLS_CERT_FILE contains only the certificate, without the intermediates |
| The certificate was replaced, but the old one is served | The files are read at startup — gameapctl panel restart is needed |
gameapctl panel https enable stops with “ACME is enabled” |
ACME takes priority over a certificate on disk. Run gameapctl panel https letsencrypt disable first |
The panel does not start after TLS_* was edited by hand |
It cannot load the configured certificate or key. Fix the pair or remove the variables; gameapctl panel https enable --cert=<path> --key=<path> avoids this by verifying and rolling back |