Profile
Back to NewsBack
GitHub Trending 26 min
Reader Mode
gameap/gameap: High-performance, free and open-source game server management panel. Pterodactyl and Pelican alternative

gameap/gameap: High-performance, free and open-source game server management panel. Pterodactyl and Pelican alternative

7 hours ago

GameAP Logo</a>

GameAP

Coverage Status</a> !GitHub Actions Workflow Status !Discord

GameAP is a free and open-source game server management panel that allows you to easily manage and deploy game servers. It provides a user-friendly web interface for managing game servers, users, and configurations.

Demo: https://demo.gameap.com

Requirements

You don't need to pre-install any dependencies. GameAP is distributed as a single binary file that includes all necessary dependencies.

You don't need any special hardware to run the application. A basic server with at least 1GB of RAM and a modern CPU should be sufficient for small to medium-sized deployments.

You can run the panel on different operating systems and database backends.

Operating System

GameAP can be installed on the following operating systems:

  • Linux (Ubuntu, Debian, CentOS, etc.)
  • Windows Server (2016, 2019, 2022, 2025), Windows 10, Windows 11
  • MacOS

Architecture

GameAP follows a three-tier architecture consisting of the web panel, daemon agents, and game servers.

Administrators interact with GameAP through a browser via HTTP/HTTPS. The panel provides a web UI, a REST API, and a WebSocket endpoint for real-time events (task output, console streams, server metrics), and stores its data in a database, which can be either PostgreSQL, MySQL, or SQLite, depending on the deployment.

Each GameAP instance talks to one or more nodes over a gRPC bidirectional stream (secured with TLS / mTLS). The daemon opens a single long-lived connection to the panel, used for both control commands (task dispatch, console input, file operations) and upstream events (heartbeats, task status, server status, metrics). On every node, a lightweight agent called GameAP Daemon runs alongside the game servers. The daemon is responsible for controlling game server processes (starting, stopping, monitoring, and configuring them). It supports Windows, Linux, and macOS and offers a wide range of configuration options.

Database

GameAP supports the following databases:

  • PostgreSQL
  • MySQL / MariaDB
  • SQLite
  • Inmemory (for testing purposes only). Not persistent, data will be lost on restart.

Quick Start with Docker

The fastest way to get started with GameAP is using Docker:

# Using Docker Compose (recommended)
docker-compose up -d

Or pull and run the pre-built image

docker pull gameap/gameap:latest docker run -d -p 8025:8025 \ -e DATABASE_DRIVER=sqlite \ -e DATABASE_URL=file:/db.sqlite?_busy_timeout=5000&_journal_mode=WAL&cache=shared \ -e ENCRYPTION_KEY=your-secret-key \ -e AUTH_SECRET=your-auth-secret \ gameap/gameap:latest

Access GameAP at http://localhost:8025

For detailed Docker deployment instructions, see DOCKER.md.

Configuration

GameAP is configured via environment variables. Below are the available configuration options:

Server Configuration

  • HTTP_HOST - HTTP server host (default: 0.0.0.0)
  • HTTP_PORT - HTTP server port (default: 8025)
  • HTTPS_PORT - HTTPS server port (default: 443)

TLS Configuration

GameAP can terminate TLS itself or, if you prefer, sit behind a reverse proxy. For self-terminated TLS the panel accepts three certificate sources, evaluated in this order:

  1. ACME / Let's Encrypt — ACME_ENABLED=true plus the ACME_* variables
below. The panel obtains and renews the certificate automatically.
  1. Static cert files — TLS_CERT_FILE + TLS_KEY_FILE.
  2. Inline cert content — TLS_CERT + TLS_KEY (raw PEM or base64-encoded).
If none of the three is configured, the HTTPS listener does not start and the panel only serves plain HTTP on HTTP_PORT.
  • TLS_CERT_FILE - Path to TLS certificate file
  • TLS_KEY_FILE - Path to TLS private key file
  • TLS_CERT - TLS certificate content (PEM or base64 encoded)
  • TLS_KEY - TLS private key content (PEM or base64 encoded)
  • TLS_FORCE_HTTPS - Force redirect HTTP to HTTPS (default: false).
When true, all HTTP_PORT requests get a 301 redirect to https://${HTTP_HOST}:${HTTPS_PORT}.

Ports. HTTPS_PORT defaults to 443. Binding to a privileged port (≤1024) needs CAP_NET_BIND_SERVICE — the systemd unit shipped by gameapctl panel install already grants it. In Docker, expose the port and run as root or set --cap-add=NET_BIND_SERVICE.

Static TLS example

HTTP_HOST=panel.example.com
HTTP_PORT=80
HTTPS_PORT=443
TLS_CERT_FILE=/etc/ssl/gameap/fullchain.pem
TLS_KEY_FILE=/etc/ssl/gameap/privkey.pem
TLS_FORCE_HTTPS=true

Let's Encrypt (ACME) Configuration

GameAP embeds the go-acme/lego ACME client and can manage Let's Encrypt certificates in-process — no external certbot, no nginx, no cron. Renewal runs in a background goroutine and the certificate is hot-swapped via tls.Config.GetCertificate, so renewals never restart the HTTPS listener.

Two challenge solvers are supported:

| Solver | Wildcards | Network requirement | Best for | |-----------|-----------|-------------------------------------|--------------------------| | http-01 | ❌ no | Inbound TCP/80 reachable from LE | Single-domain panels | | dns-01 | ✅ yes | API access to your DNS provider | Wildcards, firewalled VMs |

If the initial issuance fails (LE unreachable, DNS provider misconfigured, …) the panel exits with code 1 — there is no silent fallback to plain HTTP. Run against the LE staging endpoint while iterating on configuration.

ACME environment variables

  • ACME_ENABLED - Enable in-process ACME (default: false)
  • ACME_CHALLENGE_TYPE - http-01 or dns-01 (default: http-01)
  • ACME_EMAIL - Account email registered with Let's Encrypt (required)
  • ACME_DOMAINS - Comma-separated list of domains. Wildcards (*.example.com)
require dns-01.
  • ACME_DIRECTORY_URL - ACME directory endpoint (default: Let's Encrypt
production; switch to https://acme-staging-v02.api.letsencrypt.org/directory for testing)
  • ACME_DNS_PROVIDER - DNS provider name when ACME_CHALLENGE_TYPE=dns-01
(currently built-in: cloudflare)
  • ACME_RENEWAL_THRESHOLD - Renew when the cert has less than this duration
remaining (default: 720h = 30 days)
  • ACME_RENEWAL_CHECK_INTERVAL - How often the background loop inspects the
certificate (default: 12h)
  • ACME_PROPAGATION_TIMEOUT - Maximum wait for DNS propagation during
dns-01 (default: 180s)
  • ACME_STORAGE_PATH - Subdirectory under the files.FileManager root used
to persist the ACME account and certificate material (default: acme). With FILES_DRIVER=local, this resolves to ${FILES_LOCAL_BASE_PATH}/acme/. With FILES_DRIVER=s3, it lives in the configured bucket — which is what enables multi-instance deployments.

HTTP-01 example

http-01 requires Let's Encrypt to reach http://${ACME_DOMAINS}/.well-known/acme-challenge/.... That means the panel must listen on port 80 (or have a reverse proxy that forwards /.well-known/acme-challenge/* to it).

HTTP_HOST=panel.example.com
HTTP_PORT=80
HTTPS_PORT=443
TLS_FORCE_HTTPS=true

ACME_ENABLED=true ACME_CHALLENGE_TYPE=http-01 [email protected] ACME_DOMAINS=panel.example.com

The /.well-known/acme-challenge/{token} route is registered ahead of the SPA fallback automatically; you do not need to configure it.

DNS-01 + Cloudflare example

dns-01 does not need port 80 to be reachable. Lego writes a TXT record at _acme-challenge. via your DNS provider's API. The Cloudflare provider reads its credentials directly from the environment.

HTTP_HOST=panel.example.com
HTTPS_PORT=443
TLS_FORCE_HTTPS=true

ACME_ENABLED=true ACME_CHALLENGE_TYPE=dns-01 [email protected] ACME_DOMAINS=*.example.com,example.com ACME_DNS_PROVIDER=cloudflare

Read by lego's cloudflare provider — scope the token to the relevant zone.

CLOUDFLARE_DNS_API_TOKEN=cf-token-with-Zone-DNS-Edit-permission

Other DNS providers will be added over time — open an issue if you need one.

Staging vs. production

Let's Encrypt enforces aggressive rate limits on the production directory (5 duplicate certs / week, 50 certs / week / registered domain, …). While testing, point at the staging endpoint to avoid getting locked out:

ACME_DIRECTORY_URL=https://acme-staging-v02.api.letsencrypt.org/directory

Browsers will warn about the staging certificate — that is expected. Switch back to the default production URL once the flow works end to end.

Multi-instance deployments

A single panel instance is fully self-contained. For horizontally scaled deployments:

  • dns-01 is the supported path. Set CACHE_DRIVER=redis (the Redis
client is also used as the distributed locker for renewals) and FILES_DRIVER=s3 so every replica reads the same certificate. Only one replica at a time talks to LE; the rest pick up the new certificate from shared storage.
  • http-01 in a multi-instance setup needs sticky session affinity for
/.well-known/acme-challenge/* at the load balancer (the challenge token lives in memory on the instance that received the Present call). dns-01 is usually less hassle.

gameapctl helper

Editing config.env by hand is fine, but the friendlier path is:

gameapctl panel letsencrypt setup
gameapctl panel letsencrypt disable

setup is an interactive wizard (also accepts --challenge, --domains, --email, --dns-provider, --staging, --env KEY=VALUE, --non-interactive flags) that writes the variables and restarts the gameap systemd service. disable clears the ACME_* keys.

Status endpoint

Admin-only GET /api/admin/letsencrypt/status returns the current ACME state, useful for monitoring dashboards and the gameapctl polling logic:

{
  "enabled": true,
  "state": "active",
  "challenge_type": "http-01",
  "domains": ["panel.example.com"],
  "dns_provider": "",
  "not_before": "2026-04-01T00:00:00Z",
  "not_after":  "2026-06-30T00:00:00Z",
  "last_renewal_at": "2026-04-01T00:00:00Z",
  "next_renewal_check_at": "2026-04-01T12:00:00Z"
}

state is one of disabled, pending, active, renewing, failed.

Database Configuration

  • DATABASE_DRIVER - Database driver (required, options: mysql, postgres, sqlite, inmemory)
  • DATABASE_URL - Database connection URL (required)
  • DATABASE_CONNECT_TIMEOUT - How long to retry the initial database connect at startup (default: 30s)
- MySQL: username:password@tcp(host:port)/database?parseTime=true - PostgreSQL: postgres://username:password@host:port/database?sslmode=disable - SQLite: file:path/to/database.db?_busy_timeout=5000&_journal_mode=WAL&cache=shared (parameters recommended for production) - Inmemory: For inmemory, this can be left empty.

Security Configuration

  • ENCRYPTION_KEY - Encryption key for sensitive data
  • AUTH_SECRET - Secret key for PASETO/JWT token generation (if not set, uses ENCRYPTION_KEY)
  • AUTH_SERVICE - Authentication service type (default: paseto)
  • AUTH_ALLOW_WEAK_PASSWORDS - When true, disables the embedded common-password blocklist check used at registration / password change (OWASP ASVS §2.1.7). Default: false. Enabling this logs a startup warning; length checks (min 12 / max 128) still apply.
  • AUTH_SSO_TICKET_TTL - Lifetime of a single-use SSO login ticket minted for another user, exchanged by external systems (a billing panel) for a logged-in customer session. Hard-capped at 120s by the issuing handler. Default: 60s. Behind a load balancer this requires a shared CACHE_DRIVER (redis/postgres): the ticket is minted on one instance and redeemed by the browser on another, so the memory driver cannot see it.

Single sign-on for a customer who administers their own panel

In the common "one panel per customer" deployment the customer is the panel's administrator, seeded from ADMIN_LOGIN / ADMIN_EMAIL / ADMIN_PASSWORD. To offer them the same "open my game panel" button, the external system signs in once with that password and calls POST /api/tokens to mint a personal access token carrying admin:user:sso; the password is not needed afterwards. A token is always bound to whoever created it, so the token it now holds belongs to that administrator.

That ownership is what makes the administrator case safe to allow at all: a ticket naming an administrator is minted only when the target is the token's own owner, so a stolen token reaches exactly the identity it already spoke for and no other. A ticket for any other administrator is refused.

The customer lands in the panel on the first click, before they have set up anything. From there the ordinary admin-MFA policy takes over, the same one a password login applies:

  • while the grace window lasts (AUTH_MFA_HARD_FAIL_DAYS, default 30 days) the
ticket yields a normal session and the panel asks them to enable two-factor authentication;
  • once the window closes the ticket yields a session scoped to the 2FA-enrollment
screens and nothing else, until they enrol;
  • afterwards the ticket only ever yields a second-factor challenge, so the
customer's authenticator is needed to get in.

Two consequences worth planning for. Until that second factor exists, a token stolen out of the external system is enough to reach an administrative session — which is what bounds the exposure to the grace window, and why setting AUTH_REQUIRE_MFA_FOR_ADMINS=false leaves it open indefinitely. And once it exists, disabling it or regenerating recovery codes needs the account password, which in this deployment only the external system knows: a customer who loses their authenticator has to come back through you.

RBAC Configuration

  • RBAC_CACHE_TTL - Role-based access control cache TTL (default: 30s)

Cache Configuration

  • CACHE_DRIVER - Cache driver (options: memory, redis, postgres, default: memory)

Redis Cache

Used when CACHE_DRIVER is set to redis.

  • CACHE_REDIS_ADDR - Redis server address (default: localhost:6379)
  • CACHE_REDIS_PASSWORD - Redis password
  • CACHE_REDIS_DB - Redis database number (default: 0)

Cache TTL

  • CACHE_TTL_RBAC - Cache TTL for RBAC data (default: 24h)
  • CACHE_TTL_GAMES - Cache TTL for games (default: 48h)
  • CACHE_TTL_NODES - Cache TTL for nodes (default: 24h)
  • CACHE_TTL_USERS - Cache TTL for users (default: 6h)
  • CACHE_TTL_PERSONAL_TOKENS - Cache TTL for personal tokens (default: 24h)
  • CACHE_TTL_SERVER_SETTINGS - Cache TTL for server settings (default: 12h)

File Storage Configuration

  • FILES_DRIVER - File storage driver (options: local, s3)

Local Storage

Used when FILES_DRIVER is set to local.

  • FILES_LOCAL_BASE_PATH - Base path for local file storage

S3 Storage

Used when FILES_DRIVER is set to s3.

  • FILES_S3_ENDPOINT - S3-compatible endpoint URL
  • FILES_S3_USE_SSL - Use SSL for S3 connections (default: true)
  • FILES_S3_ACCESS_KEY_ID - S3 access key ID
  • FILES_S3_SECRET_ACCESS_KEY - S3 secret access key
  • FILES_S3_BUCKET - S3 bucket name

Chunked Upload Sessions

Used by the resumable file-manager upload endpoints (/api/file-manager/{server}/upload/sessions).

  • FILES_UPLOAD_CHUNK_SIZE - Server-decided chunk size returned to clients. Accepts plain bytes (8388608) or human-readable sizes with binary suffixes (8M, 8MB, 8MiB, 1000KB, 2GB). Default: 8M.
  • FILES_UPLOAD_SESSION_TTL - How long an in-progress upload session lives before janitor reclaims it (default: 24h)
  • FILES_UPLOAD_MAX_CHUNKS - Hard cap on chunks per file; bounds the maximum file size to FILES_UPLOAD_CHUNK_SIZE × FILES_UPLOAD_MAX_CHUNKS (default: 1000000)
  • FILES_UPLOAD_JANITOR_INTERVAL - How often the background janitor scans for expired upload sessions (default: 1h)

Legacy Configuration

  • LEGACY_PATH - Path to legacy GameAP installation (default: /var/www/gameap/)
  • LEGACY_ENV_PATH - Path to legacy .env file (default: /var/www/gameap/.env)

Global API Configuration

  • GLOBAL_API_URL - Global GameAP API URL for game updates (default: https://api.gameap.com)

Logger Configuration

  • LOGGER_LEVEL - Log level (options: debug, info, warn, error, default: info)
  • LOGGER_LOG_DB_QUERIES - Enable database query logging (default: false)

UI Configuration

  • DEFAULT_LANGUAGE - Default UI language code

Plugins Configuration

Every plugin setting used to be spelled PLUGIN_…; only the loader switches carried the PLUGINS_ prefix. All of them now use PLUGINS_, and the two compilation-cache switches became PLUGINS_RUNTIME_CACHE_ENABLED and PLUGINS_RUNTIME_CACHE_DIR so that PLUGINS_CACHE_ belongs to the gameap-cache host library alone. The former names are still accepted and translated at startup, with a warning naming the replacement; they will be removed in a future release.

  • PLUGINS_DISABLED - Disable plugins support (default: false)
  • PLUGINS_AUTOLOAD - Comma-separated wasm files from the plugins directory to register at startup
  • PLUGINS_STRICT_LOAD - Refuse to start when any plugin fails to load (default: false; a broken plugin is marked with status error and skipped)
  • PLUGINS_RUNTIME_CACHE_ENABLED - Cache compiled wasm between loads (default: true)
  • PLUGINS_RUNTIME_CACHE_DIR - Local directory for the compilation cache so panel restarts do not recompile every plugin (default: gameap/plugins in the user cache directory, e.g. /var/lib/gameap/.cache/gameap/plugins for the gameap user gameapctl creates; in-memory only when the user has no cache directory). The directory must be writable by the panel user alone, otherwise the cache stays in memory. Compiled code no load used for a week is deleted at startup
  • PLUGINS_RUNTIME_COMPILE_WORKERS - Goroutines compiling one plugin module (default: 0, one per CPU the panel may use)
  • PLUGINS_RUNTIME_MAX_MEMORY - Linear memory cap per plugin module (default: 256M, 0 = wazero default of 4 GiB)
  • PLUGINS_RUNTIME_MAX_MODULE_SIZE - Maximum wasm file size accepted for install and load (default: 128M, 0 = unlimited)
  • PLUGINS_PERMISSIONS_ENFORCE - Apply the recorded plugin permission grants (default: false; a future release will default it to true). While off, grants are recorded, shown and editable but every check passes, giving plugin developers time to declare theirs. Set it identically on every panel instance; GET /api/admin/plugins/loaded reports the answering instance's value as permissions_enforced
  • PLUGINS_PERMISSIONS_CACHE_TTL - How long granted permissions stay cached in the instance's memory (default: 30s, 0 = read the plugin record on every check). A grant change is announced over pub/sub and drops the cache on every instance, so this only bounds the drift while the broker is unreachable
  • PLUGINS_RECOVERY_ENABLED - Reload plugins the runtime disabled (guest call timeout, guest exit) with exponential backoff (default: true)
  • PLUGINS_RECOVERY_INITIAL_DELAY - Wait before the first automatic reload (default: 30s; doubles every attempt)
  • PLUGINS_RECOVERY_MAX_DELAY - Cap for the backoff (default: 10m)
  • PLUGINS_RECOVERY_MAX_ATTEMPTS - Consecutive reloads before the plugin stays in status error until an operator reloads it (default: 5)
  • PLUGINS_SCHEDULER_MIN_INTERVAL - Shortest interval a plugin may register a scheduled task with (default: 1s)
  • PLUGINS_SCHEDULER_MAX_TASKS_PER_PLUGIN - Scheduled tasks one plugin may register (default: 32)
  • PLUGINS_SCHEDULER_CALL_TIMEOUT - Budget for one task handler call when the task sets no timeout of its own (default: 60s)
  • PLUGINS_SCHEDULER_MAX_CALL_TIMEOUT - Ceiling for a per-task timeout override (default: 5m)
  • PLUGINS_SCHEDULER_MAX_RETRIES / PLUGINS_SCHEDULER_MAX_RETRY_DELAY / PLUGINS_SCHEDULER_MAX_JITTER - Caps on the retry policy a task may ask for (default: 10 / 10m / 30s)
  • PLUGINS_SCHEDULER_REFRESH_INTERVAL - How often task definitions are re-read to pick up registrations made on other instances (default: 30s)
  • PLUGINS_NODEFS_MAX_INLINE - Largest file a plugin may download or upload in one gameap-nodefs call (default: 32M, 0 = unlimited)
  • PLUGINS_NODEFS_PATH_POLICY - Where plugins may point gameap-nodefs, gameap-nodecmd working directories and file references: unrestricted (default, anything the daemon permits), node_workpath (inside the node's work path) or server_dirs (inside a game server directory on that node). Paths with .. segments are refused in every mode
  • PLUGINS_NODEFS_ALLOWED_PATHS - Comma-separated absolute roots allowed on top of a restricted path policy (e.g. /opt/steamcmd)
  • PLUGINS_SYNC_DISABLED - Stop reconciling plugins against the database on this instance; each instance then only applies its own changes (default: false)
  • PLUGINS_SYNC_REFRESH_INTERVAL - How often an instance re-reads the plugin table to pick up changes made elsewhere; pubsub hints apply them sooner (default: 60s)
  • PLUGINS_SYNC_MIN_BACKOFF / PLUGINS_SYNC_MAX_BACKOFF - Retry window for a plugin this instance could not load (default: 15s / 15m)
  • PLUGINS_STORAGE_MAX_KEYS_PER_PLUGIN - Entries one plugin may keep in gameap-storage (default: 10000)
  • PLUGINS_STORAGE_MAX_VALUE - Largest single gameap-storage payload (default: 1M)
  • PLUGINS_STORAGE_MAX_TOTAL - Sum of all gameap-storage payloads of one plugin (default: 64M)
  • PLUGINS_CACHE_MAX_VALUE - Largest single gameap-cache value (default: 1M, 0 = unlimited); every plugin has its own cache namespace
  • PLUGINS_SECRETS_MAX_KEYS_PER_PLUGIN - Secrets one plugin may keep in gameap-secrets (default: 64)
  • PLUGINS_SECRETS_MAX_VALUE - Largest plaintext of a single secret (default: 8K)
  • PLUGINS_SECRETS_REQUIRE_ENCRYPTION - Refuse gameap-secrets writes while ENCRYPTION_KEY is unset instead of keeping them in plaintext (default: true)
  • PLUGINS_HTTP_MAX_TIMEOUT - Ceiling for the per-request timeout a plugin asks for in gameap-http; a longer one is clamped (default: 30s)
  • PLUGINS_NET_MAX_TIMEOUT - Ceiling for one gameap-net operation - the dial plus every read and write (default: 10s)
  • PLUGINS_NET_READ_BUFFER - Largest single gameap-net receive a plugin may request (default: 64K)
  • PLUGINS_RATELIMIT_NODECMD_RPS / PLUGINS_RATELIMIT_NODECMD_BURST - Per-plugin token bucket for gameap-nodecmd (default: 5 / 20; RPS 0 = no limit)
  • PLUGINS_RATELIMIT_SERVERCONTROL_RPS / PLUGINS_RATELIMIT_SERVERCONTROL_BURST - Server control, daemon task creation, server and server-setting writes (default: 5 / 20)
  • PLUGINS_RATELIMIT_NODEFS_RPS / PLUGINS_RATELIMIT_NODEFS_BURST - Every gameap-nodefs operation (default: 50 / 200)
  • PLUGINS_RATELIMIT_HTTP_RPS / PLUGINS_RATELIMIT_HTTP_BURST - gameap-http requests (default: 20 / 50)
  • PLUGINS_RATELIMIT_RBAC_RPS / PLUGINS_RATELIMIT_RBAC_BURST - gameap-rbac calls (default: 10 / 50)
  • PLUGINS_RATELIMIT_SSH_RPS / PLUGINS_RATELIMIT_SSH_BURST - Every gameap-ssh call, polling a running command included (default: 20 / 60)
Rate limits are per panel instance; a refused call answers with a rate limited: ... error in the host response and the plugin keeps running. Plugin grants (manage_servers, node_commands, files, files_read, listen_events, manage_rbac, secrets, ssh) are managed per plugin in the admin UI or through PUT /api/admin/plugins/{id}/permissions; see pkg/plugin/README.md.

Plugins across instances

Several panel instances sharing one database keep their plugins in step on their own: the plugin table is the desired state, and every instance applies an install, update, uninstall, reload or permission change made on any other instance — immediately when the instances share a pubsub (PUBSUB_DRIVER=redis), otherwise on the next PLUGINS_SYNC_REFRESH_INTERVAL pass. Set PUBSUB_INSTANCE_ID to a stable, distinct value per instance so log lines, audit records and NODE_ONLINE / NODE_OFFLINE plugin events name the instance. Plugins installed from the store are re-downloaded by an instance that lacks the file (and verified against the recorded checksum); plugins uploaded from a file are only recoverable when FILES_DRIVER points at shared storage such as S3. GET /api/admin/plugins/loaded always describes the answering instance (loaded, sync), while status / error are the shared record.

Metrics

  • METRICS_TOKEN - Bearer token for the Prometheus scrape endpoint GET /metrics; empty (default) leaves the endpoint unregistered. The endpoint exposes gameap_plugin_* metrics (host/guest calls, refusals, events, disables, memory) plus the Go runtime and process collectors.

Plugin Capabilities Configuration

Bounds on what installed plugins may do through the host libraries. Defaults are strict: a compromised plugin must not be able to pivot from the panel into private networks or cloud metadata endpoints.

  • PLUGINS_HTTP_BLOCK_PRIVATE_IPS - Refuse plugin HTTP requests to loopback/private/link-local addresses (default: true)
  • PLUGINS_HTTP_ALLOWED_SCHEMES - Comma-separated URL schemes plugins may fetch (default: https)
  • PLUGINS_HTTP_ALLOWED_HOSTS - Hosts exempt from the private-IP block; empty leaves the blocklist as the only gate
  • PLUGINS_HTTP_MAX_TIMEOUT - Ceiling for a plugin's own request timeout (default: 30s)
  • PLUGINS_HTTP_MAX_REDIRECTS - Redirect limit (default: 5)
  • PLUGINS_HTTP_RESPONSE_HEADER_ALLOWLIST - Extra response headers passed back to the plugin on top of the built-in list (Content-Type, Content-Length, Content-Encoding, Content-Language, Last-Modified, ETag, Cache-Control, Date, Location, Expires, Vary). Credential-bearing headers such as Set-Cookie and WWW-Authenticate are never passed through
  • PLUGINS_NET_ENABLED - Enable the plugin socket library used by custom RCON/Query protocols (default: true)
  • PLUGINS_NET_BLOCK_PRIVATE_IPS - Refuse game-server connections to private addresses (default: false; self-hosted servers commonly live on private networks)
  • PLUGINS_NET_ALLOWED_HOSTS - Hosts exempt from that block
  • PLUGINS_NET_MAX_TIMEOUT - Ceiling for a single read/write (default: 10s)
  • PLUGINS_NET_READ_BUFFER - Cap on a single read (default: 64K)
  • PLUGINS_NET_MAX_CONNECTIONS - Open connections per plugin (default: 8)
SSH is the one capability where a plugin names its own target, so a machine can be reached before it has a daemon. It is off until an operator turns it on:
  • PLUGINS_SSH_ENABLED - Enable the gameap-ssh host library (default: false)
  • PLUGINS_SSH_BLOCK_PRIVATE_IPS - Refuse SSH to loopback/private/link-local addresses (default: true). Cloud-metadata addresses are blocked regardless
  • PLUGINS_SSH_ALLOWED_HOSTS - Hosts exempt from that block, for panels whose dedicated servers live on a private network
  • PLUGINS_SSH_MAX_CONNECTIONS - Open SSH connections per plugin (default: 8)
  • PLUGINS_SSH_MAX_OPERATIONS - Concurrently running commands per plugin (default: 16)
  • PLUGINS_SSH_CONNECT_TIMEOUT - Budget for dial, handshake and authentication (default: 30s)
  • PLUGINS_SSH_MAX_EXEC_TIMEOUT - Ceiling for one remote command (default: 30m)
  • PLUGINS_SSH_IDLE_TIMEOUT - Close a connection nothing has run on for this long (default: 10m)
  • PLUGINS_SSH_MAX_OUTPUT_BYTES - Captured stdout/stderr per command; the head is kept (default: 1048576)
  • PLUGINS_SSH_MAX_STDIN_BYTES - Cap on what a plugin may pipe into a command (default: 1048576)
  • PLUGINS_SSH_ALLOW_ACCEPT_ANY_HOST_KEY - Permit the accept_any host key policy (trust-on-first-use); disable to force pinned keys (default: true)
  • PLUGINS_SSH_OPERATION_RETENTION - Keep a finished command (with its captured output) readable for late polls (default: 10m)
  • PLUGINS_SSH_MAX_RETAINED_OPERATIONS - Finished commands kept per plugin; the oldest are evicted first (default: 64)
  • PLUGINS_SSH_KEEPALIVE_INTERVAL - Pacing of liveness probes on open connections; the effective sweep is floored at one second (default: 30s)
  • PLUGINS_SSH_COMPLETION_CALL_TIMEOUT - Budget for one completion callback into the plugin (default: 30s)
  • PLUGINS_SSH_BUSY_RETRY_DELAY - Pause between completion callback retries while the plugin is busy (default: 2s)
  • PLUGINS_SSH_BUSY_RETRIES - Completion callback retries before the callback is dropped (default: 5)

Plugin Store Configuration

  • PLUGINS_STORE_URL - GameAP plugin store URL (default: https://plugins.gameap.dev/api)
  • PLUGINS_STORE_LICENSE_KEY - License key for plugin store

CAPTCHA Configuration

Optional CAPTCHA protection for the login endpoint (POST /api/auth/login) against automated credential stuffing. Disabled until CAPTCHA_PROVIDER is set. The login form discovers the provider and public site key through GET /api/config/public; the secret key is never exposed there. The token is sent as the captcha field of the login request and is verified before the user lookup.

  • CAPTCHA_PROVIDER - CAPTCHA provider (options: recaptcha_v2, recaptcha_v3, turnstile; empty disables CAPTCHA, default: empty)
  • CAPTCHA_SITE_KEY - Public site key for the provider widget (safe to expose to browsers)
  • CAPTCHA_SECRET_KEY - Server-side secret key used to verify tokens (kept server-side)
  • CAPTCHA_MIN_SCORE - Pass threshold for reCAPTCHA v3 only, 0.0–1.0; ignored by the checkbox/Turnstile providers (default: 0.5)
  • CAPTCHA_FAIL_OPEN - Allow login when the provider's verify call itself fails (network/5xx). Default false (fail-closed: a verification outage blocks login with 503)
  • CAPTCHA_VERIFY_URL - Override the provider's siteverify endpoint (egress proxies, testing). Empty uses the provider default

HTTP Security Headers Configuration

Global response headers emitted on every HTTP/HTTPS response — HSTS, X-Content-Type-Options, X-Frame-Options, Referrer-Policy and a Content-Security-Policy. Secure defaults are on out-of-the-box; every directive is env-overridable so deployments with plugins or unusual reverse-proxy setups can extend the policy without patching the binary.

The generated CSP automatically allow-lists exactly the origins required by the configured CAPTCHA provider (Google reCAPTCHA scripts/iframes for recaptcha_v2/recaptcha_v3; challenges.cloudflare.com for turnstile); with CAPTCHA disabled no third-party origins appear in the policy.

HSTS is only emitted on TLS requests (r.TLS != nil, X-Forwarded-Proto: https from a trusted reverse proxy, or TLS_FORCE_HTTPS=true) so plain-HTTP dev sessions are never poisoned.

  • SECURITY_HEADERS_ENABLED - Master switch for all security headers (default: true)
  • SECURITY_HSTS_ENABLED - Toggle Strict-Transport-Security emission (default: true)
  • SECURITY_HSTS_MAX_AGE - HSTS max-age in seconds (default: 31536000, i.e. 1 year)
  • SECURITY_HSTS_INCLUDE_SUBDOMAINS - Append includeSubDomains (default: false — turn on only after every subdomain is HTTPS)
  • SECURITY_HSTS_PRELOAD - Append preload (default: false — only enable when intending to submit to the HSTS preload list)
  • SECURITY_CONTENT_TYPE_OPTIONS - Emit X-Content-Type-Options: nosniff (default: true)
  • SECURITY_FRAME_OPTIONS - X-Frame-Options value, empty to omit (default: SAMEORIGIN)
  • SECURITY_REFERRER_POLICY - Referrer-Policy value, empty to omit (default: strict-origin-when-cross-origin)
  • SECURITY_CSP_ENABLED - Emit a Content-Security-Policy header (default: true)
  • SECURITY_CSP_REPORT_ONLY - Use Content-Security-Policy-Report-Only instead of enforcing — for staged rollout (default: false)
  • SECURITY_CSP_POLICY - Verbatim CSP override; when set, the generated policy and the additive SECURITY_CSP_EXTRA_* lists are ignored (default: empty)
  • SECURITY_CSP_REPORT_URI - Endpoint appended as report-uri to receive CSP violation reports (default: empty)
  • SECURITY_CSP_EXTRA_SCRIPT_SRC - Comma-separated extra origins appended to script-src (e.g. a plugin's CDN)
  • SECURITY_CSP_EXTRA_STYLE_SRC - Comma-separated extra origins appended to style-src
  • SECURITY_CSP_EXTRA_CONNECT_SRC - Comma-separated extra origins appended to connect-src
  • SECURITY_CSP_EXTRA_IMG_SRC - Comma-separated extra origins appended to img-src
  • SECURITY_CSP_EXTRA_FRAME_SRC - Comma-separated extra origins appended to frame-src
  • SECURITY_CSP_EXTRA_FONT_SRC - Comma-separated extra origins appended to font-src

Example Configuration

# Server
HTTP_HOST=panel.example.com
HTTP_PORT=8025
HTTPS_PORT=443

--- TLS: pick ONE of (a), (b) or (c). Leave all commented out for plain HTTP. ---

(a) Static cert files

TLS_CERT_FILE=/etc/ssl/gameap/fullchain.pem

TLS_KEY_FILE=/etc/ssl/gameap/privkey.pem

TLS_FORCE_HTTPS=true

(b) ACME / Let's Encrypt — HTTP-01 (port 80 must be reachable from LE)

HTTP_PORT=80

TLS_FORCE_HTTPS=true

ACME_ENABLED=true

ACME_CHALLENGE_TYPE=http-01

[email protected]

ACME_DOMAINS=panel.example.com

(c) ACME / Let's Encrypt — DNS-01 + Cloudflare (supports wildcards)

TLS_FORCE_HTTPS=true

ACME_ENABLED=true

ACME_CHALLENGE_TYPE=dns-01

[email protected]

ACME_DOMAINS=*.example.com,example.com

ACME_DNS_PROVIDER=cloudflare

CLOUDFLARE_DNS_API_TOKEN=cf-token-with-Zone-DNS-Edit-permission

Use the staging endpoint while iterating to avoid LE rate limits:

ACME_DIRECTORY_URL=https://acme-staging-v02.api.letsencrypt.org/directory

Database

DATABASE_DRIVER=mysql DATABASE_URL=gameap:password@tcp(localhost:3306)/gameap?parseTime=true

Security

ENCRYPTION_KEY=your-secure-encryption-key-here AUTH_SECRET=your-secure-auth-secret-here AUTH_SERVICE=paseto

Cache

CACHE_DRIVER=memory

For Redis cache (also enables the distributed lock used by ACME renewals):

CACHE_DRIVER=redis

CACHE_REDIS_ADDR=localhost:6379

File Storage

FILES_DRIVER=local FILES_LOCAL_BASE_PATH=/var/lib/gameap/files

For multi-instance deployments switch to S3 so every replica sees the same

ACME storage:

FILES_DRIVER=s3

FILES_S3_ENDPOINT=https://s3.example.com

FILES_S3_BUCKET=gameap

FILES_S3_ACCESS_KEY_ID=...

FILES_S3_SECRET_ACCESS_KEY=...

Legacy

LEGACY_PATH=/var/www/gameap/

Global API

GLOBAL_API_URL=https://api.gameap.com

Logger

LOGGER_LEVEL=info

Plugins

PLUGINS_DISABLED=false

Plugin capabilities — defaults are strict; SSH is off until enabled

PLUGINS_SSH_ENABLED=false

PLUGINS_SSH_BLOCK_PRIVATE_IPS=true # cloud metadata is blocked either way

PLUGINS_SSH_ALLOWED_HOSTS=node1.internal,node2.internal

PLUGINS_SSH_MAX_EXEC_TIMEOUT=30m

Plugin Store

PLUGINS_STORE_URL=https://plugins.gameap.dev/api

PLUGINS_STORE_LICENSE_KEY=your-license-key

CAPTCHA (login protection) — leave CAPTCHA_PROVIDER empty to disable

CAPTCHA_PROVIDER=turnstile # recaptcha_v2 | recaptcha_v3 | turnstile

CAPTCHA_SITE_KEY=your-public-site-key

CAPTCHA_SECRET_KEY=your-server-side-secret-key

CAPTCHA_MIN_SCORE=0.5 # reCAPTCHA v3 only

CAPTCHA_FAIL_OPEN=false # true = allow login if the provider is unreachable

HTTP security headers — secure defaults are on; uncomment to tune

SECURITY_HSTS_INCLUDE_SUBDOMAINS=true # only after every subdomain is HTTPS

SECURITY_HSTS_PRELOAD=true # only if submitting to hstspreload.org

SECURITY_CSP_REPORT_ONLY=true # stage CSP without enforcing first

SECURITY_CSP_REPORT_URI=https://csp.example.com/report

SECURITY_CSP_EXTRA_SCRIPT_SRC=https://cdn.plugin.example

Chat with me