Nimbusv1.0.0

Core Concepts

Fundamental building blocks of Nimbus — groups, services, templates, scaling, server software, JDK management, and ports.

This page explains the fundamental building blocks of Nimbus. Understanding these concepts will help you configure and operate your network effectively.

Groups

A group is a definition for a type of server. It specifies the software, version, resource limits, scaling rules, and lifecycle behavior. Groups are configured as TOML files in the config/groups/ directory.

Directory Structure
config/groups/
├── proxy.toml      # Velocity proxy
├── lobby.toml      # Lobby servers
└── bedwars.toml    # BedWars game servers

Group Types

TypeBehaviorUse Case
STATICPersistent instances. Data preserved across restarts. Stored in services/static/.Proxy servers, build servers
DYNAMICEphemeral instances. Created fresh from templates each time. Stored in services/temp/.Lobbies, game servers, any scalable workload

Naming Convention

Group names use PascalCase: Lobby, BedWars, SkyWars, Proxy.

Example Group Config

config/groups/bedwars.toml
[group]
name = "BedWars"
type = "DYNAMIC"
template = "bedwars"
software = "PAPER"
version = "1.21.4"

[group.resources]
memory = "2G"
max_players = 16

[group.scaling]
min_instances = 1
max_instances = 10
players_per_instance = 16
scale_threshold = 0.8
idle_timeout = 300

[group.lifecycle]
stop_on_empty = true
restart_on_crash = true
max_restarts = 5

[group.jvm]
optimize = true

Services

A service is a running instance of a group. When Nimbus starts a service, it creates a process from the group's template, assigns a port, and monitors its lifecycle.

Naming

Services are named <GroupName>-<N>, where N is an incrementing number:

  • Proxy-1, Lobby-1, Lobby-2, BedWars-1, BedWars-3

Service States

Every service moves through a lifecycle of states:

Service Lifecycle
PREPARING → PREPARED → STARTING → READY → DRAINING → STOPPING → STOPPED

                                    └──→ CRASHED
StateDescription
PREPARINGTemplate files are being copied to the service directory.
PREPAREDTemplate copy is complete. Service is staged in the warm pool, waiting to be started. Only reached when warm_pool_size > 0 is configured for the group.
STARTINGJVM process has launched. Nimbus watches stdout for the "Done" pattern.
READYServer is fully started and accepting player connections.
DRAININGThe load balancer is draining existing connections before the service is stopped. New players are not routed to a draining service.
STOPPINGGraceful shutdown has been initiated.
STOPPEDProcess has exited cleanly.
CRASHEDProcess exited unexpectedly. May auto-restart based on lifecycle config.

Static vs Dynamic Lifecycle

Static services (like a proxy) keep their data between restarts. Their files live in services/static/Proxy-1/ and persist across Nimbus restarts.

Dynamic services (like lobbies and game servers) are created fresh each time from their template. When stopped, their services/temp/ directory is cleaned up. This ensures every new instance starts from a known good state.

State Sync

For static services that need to float across cluster nodes, Nimbus provides state sync — a mechanism that keeps a canonical copy of a service's working directory on the controller and synchronises it to and from agent nodes automatically.

How it works:

  1. Canonical store — The controller holds the authoritative copy of the service's data under services/state/<name>/ (for groups) or dedicated/<name>/ (for dedicated services).
  2. Pull on start — Before launching, the agent compares a manifest of its local files against the controller's canonical manifest. Only changed or missing files are downloaded (staged atomically, hardlinks used where possible to minimise disk I/O).
  3. Push on stop — After a graceful shutdown, the agent pushes its working directory delta back to the controller. Only files that changed since the last pull are uploaded.

Data-loss model: A graceful stop results in zero data loss. An unexpected crash means any changes since the last successful push are lost — the service will resume from the previous canonical snapshot on the next start.

Enabling state sync:

config/groups/lobby.toml
[group.sync]
enabled = true

See group config reference for the full option list.

State sync is what enables static services to be placed on remote agent nodes via [group.placement] node = "worker-1". Without sync, static services always run on the controller where their data already lives.

Custom States

Services can report custom states via the Nimbus SDK (e.g., INGAME, ENDING, WAITING). A service with a custom state is excluded from routing — the scaling engine won't count it toward available capacity, and the proxy won't send new players to it.

Custom states are perfect for game servers. Set the state to INGAME when a match starts, and the proxy will stop sending new players while the scaling engine starts fresh instances for the queue.

Templates

Templates define what files get copied into a service instance when it starts. Nimbus uses a layered template system:

Template Layers

Directory Structure
templates/
├── global/            # Layer 1: All backend servers (user-owned)
├── global_proxy/      # Layer 1: All proxy servers (user-owned)
│   └── plugins/
│       └── nimbus-bridge/
│           └── bridge.json
└── lobby/             # Layer 2: Group-specific
    ├── server.jar
    ├── plugins/
    │   └── viabackwards.jar
    └── server.properties

When Nimbus starts a Lobby service, it merges templates in order:

  1. templates/global/ — Copied first. Contains files shared across all backend servers (Paper, Purpur, Leaf, etc.).
  2. templates/lobby/ — Copied second, overwriting any conflicts. Contains the group-specific server JAR, plugins, and configuration.

For proxy services, templates/global_proxy/ is used instead of templates/global/.

Runtime-deployed plugins

From v0.9.0 on, Nimbus-managed plugins (the SDK, the Bridge, and every module's companion plugin — Punishments, Resource Packs, Perms, Display, …) are no longer written into templates. They are deployed on every service prepare with REPLACE_EXISTING, so deleted or modified copies self-heal on the next start.

Only config files that admins may want to edit — like plugins/nimbus-bridge/bridge.json — and Bedrock components (Geyser on the proxy, Floodgate on both sides, when Bedrock support is enabled) are still written into templates. Everything else stays in templates/ only if you put it there.

What to Put in Templates

LocationContents
templates/global/plugins/Your own plugins shared across all backend servers (Nimbus's SDK is not here — see the runtime-deploy callout above)
templates/global_proxy/plugins/Your own plugins shared across all proxies (Nimbus's Bridge is not here)
templates/<group>/Server JAR (auto-downloaded), group-specific plugins, server.properties, world files, etc.

Scaling

Nimbus automatically manages the number of running instances for each dynamic group based on player count.

How It Works

The Scaling Engine runs on a configurable interval (default: every 10 seconds). For each dynamic group, it:

  1. Pings all ready services to get current player counts
  2. Evaluates scale-up rules — Do we need more capacity?
  3. Evaluates scale-down rules — Are there idle instances to remove?

Scale Up

A new instance is started when:

  • Total players across routable instances exceed players_per_instance * routable_count * scale_threshold
  • The current instance count is below max_instances

Example: With players_per_instance = 16, scale_threshold = 0.8, and 1 instance holding 13 players, Nimbus sees 13 > (16 * 1 * 0.8 = 12.8) and starts a second instance.

Scale Down

An instance is stopped when:

  • It has zero players
  • It has been idle longer than idle_timeout seconds (0 = never scale down idle)
  • The group still has more than min_instances running

Lobby scaling

For lobbies, set idle_timeout = 0 to prevent them from being scaled down when empty. Lobbies should always be available for players to connect to.

Scaling Config Reference

SettingDefaultDescription
min_instances1Minimum number of instances always running
max_instances4Maximum number of instances the engine will create
players_per_instance40Target players per instance for capacity calculation
scale_threshold0.8Percentage of capacity that triggers scale-up (0.0 - 1.0)
idle_timeout0Seconds before an empty instance is stopped (0 = never)

Server Software

Nimbus auto-downloads server JARs from official APIs. The following platforms are supported:

Vanilla & Plugin Servers

SoftwareTypeAPI Source
PaperMinecraft serverPaperMC API
PufferfishMinecraft server (Paper fork, high-performance)Pufferfish CI
PurpurMinecraft server (Paper fork)Purpur API
LeafMinecraft server (Paper fork, performance + stability)Leaf API
FoliaMinecraft server (regionized multithreading)PaperMC API
VelocityProxyPaperMC API
CustomAnyYou provide the JAR manually

Modded Servers

SoftwareTypeAuto-downloadProxy Forwarding
ForgeModded MinecraftInstaller from Forge Mavenproxy-compatible-forge (auto-installed)
FabricModded MinecraftLauncher JAR from Fabric MetaFabricProxy-Lite + Fabric API (auto-installed)
NeoForgeModded MinecraftInstaller from NeoForge Mavenproxy-compatible-forge (auto-installed)
QuiltModded MinecraftVia Fabric compatibilityFabricProxy-Lite (auto-installed)

Cardboard for Fabric (BETA)

Fabric servers can optionally install Cardboard to run Bukkit/Paper plugins alongside Fabric mods. During group creation, Nimbus offers to download Cardboard and its dependency iCommon automatically. This is experimental — not all plugins will work correctly.

Folia

Folia uses regionized multithreading which breaks most Bukkit/Paper plugins. The Nimbus plugins (SDK, NimbusPerms, NimbusDisplay) are fully Folia-compatible using region-aware scheduling.

Modded servers are first-class citizens in Nimbus. When you create a Forge, Fabric, or NeoForge group, Nimbus:

  1. Downloads the modloader — Runs the installer automatically (Forge/NeoForge) or fetches the launcher JAR (Fabric)
  2. Installs the proxy forwarding mod — Downloads the correct mod from Modrinth so players can connect through Velocity
  3. Configures forwarding — Sets modern or legacy mode in the mod's config, copies the forwarding secret
  4. Handles startup — Uses the correct launch command (@libraries/... args file for Forge/NeoForge, -jar server.jar for Fabric)

You can also import entire modpacks from Modrinth with a single command -- see Modpack Import.

EULA

Nimbus automatically accepts the Minecraft EULA for Paper, Pufferfish, Purpur, Leaf, and Folia servers. You don't need to manually edit eula.txt.

Via Plugins

For cross-version compatibility, Nimbus can download and manage Via plugins on backend servers only (never on the proxy):

  • ViaVersion — Lets players with newer Minecraft clients join older servers
  • ViaBackwards — Lets players with older clients join newer servers (requires ViaVersion — auto-included if missing)
  • ViaRewind — Extends backwards support down to 1.7/1.8 clients (requires ViaBackwards)

Automatic JDK Management

Different Minecraft versions require different Java versions. Nimbus handles this automatically -- you don't need to install multiple JDKs manually.

Java version mapping

Minecraft VersionRequired JavaMax Java
1.16.x and belowJava 16Java 16
1.17.xJava 16No limit
1.18.x - 1.20.4Java 17No limit
1.20.5 - 1.21+Java 21No limit
26.x+ (new scheme)Java 25No limit
Velocity (all versions)Java 21No limit

How it works

When Nimbus needs to start a server, it resolves the correct Java executable through several layers:

  1. Per-group override — If the group config has a java_path set, that is used directly
  2. Configured paths — Paths set in the [java] section of nimbus.toml
  3. Auto-detection — Nimbus scans for installed JDKs in common locations:
    • Nimbus's own jdks/ cache directory
    • Environment variables (JAVA_16_HOME, JAVA_17_HOME, JAVA_21_HOME, etc.)
    • JAVA_HOME
    • System directories (/usr/lib/jvm, ~/.sdkman/candidates/java, ~/.jdks, etc.)
  4. Auto-download — If no compatible JDK is found, Nimbus downloads one from Eclipse Adoptium (Temurin) automatically
Nimbus — JDK Auto-Download
[12:00:01]  Detected Java installations: Java 17, Java 21
[12:00:05]  No Java 16 found — downloading automatically...
[12:00:08]  Downloaded Java 16 (187.3 MB), extracting...
[12:00:12]  Java 16 installed to jdks/java-16/bin/java

Downloaded JDKs are cached in the jdks/ directory inside your Nimbus installation. They persist across restarts and are reused automatically.

For most modern setups (1.20.5+), you only need Java 21. Nimbus will auto-detect it from your system PATH. The auto-download feature is most useful when running legacy servers (1.8.x - 1.16.x) alongside modern ones. Java 16 is the minimum supported runtime for all Nimbus plugins.

Ports

Nimbus automatically allocates ports for all services:

Port RangeUsed For
25565Proxy (Velocity). This is the port players connect to.
30000 - 39999Backend servers (Paper, Purpur, Leaf, etc.). Allocated sequentially.

Ports are allocated when a service starts and released when it stops. Backend ports are checked for availability before assignment — if a port is already in use by another process, Nimbus skips it.

Players never connect to backend ports directly. They connect to the proxy on port 25565, and the proxy routes them to the appropriate backend server.

Shutdown Order

When Nimbus shuts down (via the shutdown command or a system signal), it stops services in a specific order to avoid disrupting players:

  1. Game servers — Dynamic instances are stopped first
  2. Lobbies — Lobby instances are stopped next
  3. Proxies — The proxy is stopped last, after all backends are down

This ensures players get kicked cleanly from game servers before the proxy goes down, rather than having their connections silently dropped.