The built-in BungeeCord permission system tops out at about 5 admins before it gets unmanageable. LuckPerms is what every running network actually uses. Install once on the proxy, install on every backend, point them at the same database, done — permissions sync everywhere automatically.

TL;DR — what you're building:

Proxy (BungeeCord/Velocity)  +  LuckPerms-Bungee/Velocity ──┐
Backend lobby (Paper)        +  LuckPerms (Bukkit)          │  all read/write the same MySQL DB
Backend pvp (Paper)          +  LuckPerms (Bukkit)          ┘

One database, one source of truth. Add a player to the admin group on any server, every other server sees it within a second.

This post assumes the proxy itself is already running — see BungeeCord network setup or the Velocity migration guide if not.

Why MySQL, not flatfile

LuckPerms supports SQLite, H2, MySQL, and a few others. For a proxy network, MySQL is the only sane choice. Flatfile / SQLite work, but they store data on whichever server LuckPerms loaded first — the others can't see edits unless you trigger a manual sync.

MySQL means every server reads and writes the same tables, and LuckPerms uses change-detection plus a messaging service (Redis, plugin-messaging, or DB polling) to push updates instantly.

A small MySQL on the same VM as the proxy is fine. 50 MB RAM, no tuning needed. If you're already running MariaDB for something else, point at that.

Install MySQL

sudo apt install mariadb-server
sudo mysql_secure_installation

Create a database and user:

CREATE DATABASE luckperms;
CREATE USER 'luckperms'@'localhost' IDENTIFIED BY 'changeme';
GRANT ALL ON luckperms.* TO 'luckperms'@'localhost';
FLUSH PRIVILEGES;

If your backends are on different boxes, grant from '%' instead of localhost and bind MariaDB to the proxy's internal network (NOT the public IP). Add a UFW rule restricting port 3306 to the network range.

Install LuckPerms — proxy

Download the right artifact for your proxy:

  • BungeeCord/Waterfall → LuckPerms-Bungee.jar
  • Velocity → LuckPerms-Velocity.jar

Drop into plugins/, restart the proxy, kill it after first start. LuckPerms generates plugins/LuckPerms/config.yml (or config.toml on Velocity).

Edit the storage section:

storage-method: mysql
data:
  address: 127.0.0.1
  database: luckperms
  username: luckperms
  password: changeme
  pool-settings:
    maximum-pool-size: 10

And the messaging section — this is what makes changes propagate:

messaging-service: sql

sql polls the DB every second for change events. Cheap, works without extra infrastructure. If you have Redis already running, use redis instead — it's instant.

Restart the proxy. Check the log:

[LuckPerms] Successfully connected to MySQL
[LuckPerms] Loaded 0 groups

Zero groups is correct on first start. We'll add some.

Install LuckPerms — every backend

Same JAR variant, different platform:

  • Paper / Spigot → LuckPerms-Bukkit.jar
  • Fabric → LuckPerms-Fabric.jar

Same config.yml storage block on every backend. Same MySQL credentials. Same messaging-service.

This is the part everyone messes up: every backend must point at the same DB. If two backends point at different databases, you've split your permissions. Symptom: a player has admin on one server, default on another, and you can't figure out why.

Restart each backend, watch for the same Successfully connected to MySQL line.

Bootstrap a permission tree

Now you can build groups from any server. I usually do it on the proxy console because that's where I'm SSH'd in anyway.

/lp creategroup default
/lp creategroup member
/lp creategroup mod
/lp creategroup admin

/lp group default permission set bungeecord.command.list true
/lp group default permission set bungeecord.command.server true

/lp group member parent add default
/lp group member permission set essentials.home true
/lp group member permission set essentials.tpa true

/lp group mod parent add member
/lp group mod permission set essentials.kick true
/lp group mod permission set essentials.tempban true

/lp group admin parent add mod
/lp group admin permission set bungeecord.command.alert true
/lp group admin permission set * true

Then add yourself:

/lp user Lukas parent set admin

Inheritance: admin → mod → member → default. Anything default has, every group has. Anything mod has, admin has. Standard role-tree pattern.

The per-server permission trap

LuckPerms has a server= context that scopes a permission to one backend. Looks useful — "give mods kick on survival but not on creative". In practice it bites you because the proxy and the backends use different server names.

The proxy sees servers by their BungeeCord/Velocity name (lobby, survival). The backend sees itself as whatever server-name is set in server.properties — often unset, defaulting to a different value.

Make them match:

  • BungeeCord config.yml server name: survival
  • That backend's server.properties: server-name=survival
  • That backend's plugins/LuckPerms/config.yml: server: survival

Then /lp user X permission set foo true server=survival works as expected on both sides. Skip this and your contexts are silently inconsistent.

Verifying sync

From any server console:

/lp user Lukas info

Shows groups, permissions, contexts. Run it on the proxy and on a backend back-to-back. If the data differs, sync is broken — usually wrong DB credentials on one side.

Backups

Database dump nightly:

mysqldump --single-transaction luckperms | gzip > /backup/lp/$(date +%F).sql.gz

--single-transaction avoids locking — dumps a consistent snapshot without blocking writes. Pattern matches rsync backups on Linux — different tool, same nightly cadence.

Keep 30 days. Restoring is gunzip < backup.sql.gz | mysql luckperms. Permissions never change in volume — daily diffs are tiny, so the storage cost is nothing.

Migration from BungeeCord built-in

If you've been running with BungeeCord's built-in permissions: block in config.yml, the migration is manual but quick. Each groups: entry becomes a /lp creategroup and parent assignment. There's no automatic importer, but for a small set of users it's faster to retype than to script.

Once everyone's migrated, comment out the permissions: and groups: blocks in config.yml to avoid confusion. The built-in checks happen before LuckPerms's, so if both define a permission with different values, the built-in wins — which is rarely what you want.

Gotchas

  • Don't forget messaging-service. Without it, LuckPerms only re-reads the DB when a server starts. You change a permission, nothing happens until the next restart.
  • Time skew kills SQL messaging. SQL messaging uses timestamps to detect changes. If your proxy and backends are on different VMs and one has clock skew of more than a few seconds, change events arrive out of order. Run chronyd or systemd-timesyncd everywhere.
  • /lp sync exists for a reason. Nine times out of ten when "permissions don't update", a /lp sync on the affected server fixes it. If it fixes it, your messaging is broken — investigate before you make it a habit.
  • Don't * permission a group casually. * is the wildcard; LuckPerms grants every permission to everyone in that group. I use it on admin because I trust admins. mod should never get * — give specific permissions.
  • Per-world contexts are slow. LuckPerms supports world=overworld style contexts. They work, but every permission check evaluates them. On a 200-player server with 50 worlds, this adds measurable CPU. Use server-level contexts where possible, world-level only when needed.

Where to go next

LuckPerms ships a web editor. Run /lp editor from a console — it gives you a one-time URL to a JSON-editor view of the entire permission tree. Edit, paste back. Faster than typing 200 commands.

For a deeper config breakdown of the proxy itself, see BungeeCord config.yml deep-dive. For the difference between the proxies, Waterfall vs BungeeCord.


Related posts