> ## Content Index
> Fetch the complete content index at: https://blog.sudowl.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# LuckPerms on BungeeCord and Velocity — Permissions That Actually Scale
- URL: https://blog.sudowl.com/luckperms-bungeecord-velocity-setup/
- Published: 2026-05-01T10:00:00.000Z
- Updated: 2026-10-11T10:16:27.000Z
- Description: Install LuckPerms on a Minecraft proxy network, sync permissions across backends, define groups and inheritance, avoid the per-server-permission trap.
- Author: Lukas
- Tags: Minecraft, Gameserver, Linux

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](/minecraft-bungeecord-network-setup/) or the [Velocity migration guide](/migrate-bungeecord-waterfall-to-velocity/) 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

```bash
sudo apt install mariadb-server
sudo mysql_secure_installation

```

Create a database and user:

```sql
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](/ufw-firewall-rules-ubuntu/) 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:

```yaml
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:

```yaml
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:

```bash
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](/rsync-backup-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](/bungeecord-config-yml-explained/). For the difference between the proxies, [Waterfall vs BungeeCord](/waterfall-vs-bungeecord-minecraft-proxy/).

---

### Related posts

- [Waterfall vs BungeeCord — Which Minecraft Proxy Should You Run?](https://blog.diengdoh.com/waterfall-vs-bungeecord-minecraft-proxy/?ref=blog.sudowl.com)
- [BungeeCord config.yml — Every Option Explained](https://blog.diengdoh.com/bungeecord-config-yml-explained/?ref=blog.sudowl.com)
- [Velocity Minecraft Proxy — Setup From Scratch in 10 Minutes](https://blog.diengdoh.com/velocity-minecraft-proxy-setup-from-scratch/?ref=blog.sudowl.com)