Resource Pack Optimization for Large Minecraft Servers

Minecraft resource pack optimization for large servers cover

Resource packs are the quickest way to make a server look like itself: a custom GUI, retextured blocks, a title screen with your logo. They are also the quickest way to add thirty seconds to every join and a support ticket that reads “my game freezes when I join.” This guide is for the people who maintain that pack — owners, admins, and the one staff member who knows how to open a zip file — and its goal is simple: help you ship something that still loads on a five-year-old laptop.

We rebuilt a sample pack from a mid-size survival network, timed cold loads on three client machines, and went to primary sources instead of folklore: the vanilla resource pack format documentation, PackSquash’s repository, Paper’s docs, and Blockbench’s export notes. Every number below is directional. Measure your own pack before you trust any of it.

Measure Before You Optimize

Most pack work starts with a tool and ends with a shrug, because nobody wrote down the starting point. Capture four numbers before you touch a texture, and keep them next to the release date.

  • Compressed size — what a player downloads. This is your bandwidth bill and your worst-case first join.
  • Resolution profile — how many files are above 16×16, and how many are 512x or larger. A handful of 2048x GUI sheets outweighs a thousand vanilla-sized blocks.
  • Audio weight — total size of the sounds folders. Uncompressed WAV files are the usual culprit.
  • Cold load time — clear the client’s pack cache, join, and time from “joining world” to a playable screen. Three runs, middle value.
MetricSample pack beforeAfter a full passWhat it usually means
Zip sizeRoughly 300–350 MBRoughly 60–90 MBBandwidth and first-join wait drop together
Files over 256xHundredsDozensVRAM and atlas stitching cost
Audio folderRoughly 80 MBUnder 20 MBOGG conversion plus trimming
Cold load time30–45 s8–15 sMostly decoding and upload to GPU

Those ranges come from one pack on three test machines. Treat the table as a map of where savings live, then find yours.

Textures: PNG Size, Atlas Layout, and Mipmaps

Compress PNGs, but know where lossy stops being free

Start lossless. Oxipng and PackSquash’s PNG recompression strip metadata and pick better filter combinations without touching a pixel. That buys a modest cut with zero risk, and it will not rescue a pack full of 2048x renders, because the pixels are the problem.

Lossy quantization reduces each image to a palette. It is acceptable on noisy surfaces players never read closely: stone variants, grass, dirt, foliage, distant terrain, particle sheets. It is a bad idea on text, GUI icons, fonts, and anything with a hard one-pixel border, where banding looks like a bug rather than a style. Quantize in a branch, then check a shop GUI, a sign, and the inventory screen at default scale before merging.

Padding, atlas layout, and mipmap bleeding

Texture bleeding — seams, flickering edges, colors leaking from a neighboring tile — is a layout problem, not a compression one. The client stitches block textures into an atlas and builds mipmaps from it, and at lower mip levels a tile samples pixels outside its own bounds. Packs that mix 16x and 128x art invite it.

  • Keep tiles square and consistently sized within a block family so the atlas packs cleanly.
  • Set padding in your atlas definition. Modern versions read it from assets/minecraft/atlases/blocks.json, and the schema has changed since 1.19.3 — confirm field names against the wiki page for your target version instead of copying an old tutorial.
  • Match resolution where you can afford it. A 16x tile beside 128x neighbors is the classic bleeding case.
  • Test at mipmap levels 0 and 4. Players control that setting and you cannot force it server-side.

Audio and Asset Cleanup

Convert audio to OGG Vorbis and trim the silence

The vanilla sound engine reads OGG Vorbis. WAV files in a pack are ignored by the client, yet authors ship them anyway — you pay for the bytes and hear nothing. Convert with ffmpeg, keep mono for positional sounds so the client can place them in 3D space, and reserve stereo for music and stingers.

ffmpeg -i raw/click.wav -ac 1 -c:a libvorbis -q:a 4 -ar 44100 assets/minecraft/sounds/custom/click.ogg

Then trim. A two-second sting with 400 ms of leading silence feels broken in game and costs bytes in every client cache. Batch-trim leading and trailing silence, and delete the source WAVs from the build folder rather than leaving them beside the OGGs.

Delete what nothing references

Every pack maintained for a year contains orphans. Walk the model JSON files, collect every textures/ and parent reference, and diff that list against the files on disk; whatever is left over may still be decoded during pack load. The same pass finds duplicate models: three copies of one custom sword under different filenames, each pointing at the same texture. Collapse them into a single model with overrides. PackSquash also prunes files that match vanilla exactly, a free win if your pack began as a full vanilla copy.

A PackSquash Pipeline You Can Run in CI

PackSquash is the practical default: it recompresses PNGs, transcodes audio to OGG, rewrites model and atlas references, prunes redundant files, and can strip data a given pack_format never reads. It is fast enough to run on every commit, which is the real value — optimization becomes a property of the pipeline instead of a chore someone remembers twice a year.

# pack.toml (trimmed; see the repository README for the full schema)
[output_file]
path = "build/server-pack.zip"

[compression]
zip_spec = "deflate"
compression_level = 9

[optimize]
lossless_png = true
transcode_ogg = true
color_quantization_target = "auto"

A GitHub Actions job that gates a pull request on pack size and publishes the hash your deploy step needs:

name: build-pack
on: [push, pull_request]
jobs:
  pack:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install PackSquash
        run: |
          curl -L -o ps.zip https://github.com/ComunidadAylas/PackSquash/releases/latest/download/packsquash-x86_64-unknown-linux-musl.zip
          unzip -o ps.zip -d /usr/local/bin && chmod +x /usr/local/bin/packsquash
      - name: Build optimized pack
        run: packsquash --config pack.toml pack/ build/server-pack.zip
      - name: Record size and hash
        run: |
          ls -lh build/server-pack.zip
          sha1sum build/server-pack.zip | tee build/sha1.txt

Check the README for the version you pin; flag names move between releases.

Delivery: CDN, Caching, MIME Types, and SHA-1

A small pack served badly still feels slow. Put the artifact behind a CDN or object storage, not on the box running your world, and set Content-Type: application/zip explicitly — some stacks guess application/octet-stream, and players behind strict proxies get a download prompt on the join screen. Leave range requests on so a failed download can resume.

  • Hash the filename on deploy. server-pack-3f9a1c.zip can be cached forever at the edge; pack.zip cannot, so every deploy is a cold cache for every player.
  • Update the SHA-1 every time. A hash that does not match the served bytes makes clients re-download on every join — the usual cause of “the pack updates constantly.”
  • Keep the previous artifact up for a few days so joins in flight during a deploy do not fail mid-download.
  • Check proxy behavior. BungeeCord, Velocity, and pack-forwarding plugins each handle the resource pack fields differently.

None of this touches tick time — for that, see [Understanding MSPT and Tick Rate: A Server Owner’s Performance Guide]. Pack work is a client-side and network-side optimization, and its natural partner is [Resource Pack Basics: Hosting, Distribution, and Forcing Packs].

Client memory, VRAM, and Bedrock via Geyser

Every high-resolution texture is uploaded to GPU memory and held for the session. A pack of 512x sheets can push a 2 GB integrated GPU into swapping, and the symptom is not a crash — it is stutter while flying, late chunk rendering, and a client that dies on alt-tab.

Bedrock changes the math. Geyser can forward packs, but Bedrock’s format is different: no pack.mcmeta, a different manifest, no Java model JSON. Anything you force through Geyser needs a separate Bedrock build, and mobile size ceilings are lower than people expect. Client pressure from mob counts is covered in [Taming Entity Counts: Reducing Mob Lag on Busy SMPs].

Pros and Cons of an Aggressive Optimization Pass

  • Pro: Faster joins and fewer “my game froze” reports, especially from laptop players.
  • Pro: Lower egress cost and a CDN bill that stops scaling with player count.
  • Pro: A repeatable build means pack changes are reviewed, hashed, and deployable by anyone on staff.
  • Con: Lossy quantization is irreversible if you delete the source textures. Keep the source project in version control.
  • Con: Pruning breaks things quietly — a sound referenced only from a plugin path can vanish with no build error.

Common Mistakes and How to Avoid Them

  • Optimizing before measuring. Keep a baseline row per release. It settles every later argument.
  • Forgetting the SHA-1. Wire the hash into deploy automation; a stale hash is invisible in testing and obvious to players.
  • Quantizing GUI and fonts. Text is where banding shows instantly. Keep those files lossless.
  • Testing only on your own machine. Your desktop is the best-case client. Ask a low-end laptop user to join after each major change.
  • Bundling the source folder. The zip should contain only what the client reads. Editors, PSDs, and .bbmodel files belong in git.

One tip from the rebuild: test on a client with no cache and a server that forces the pack. The bleeding atlas and the stale hash both appeared only on a cold join.

Frequently Asked Questions

What is a reasonable resource pack size for a Minecraft server?

Aim for under 50 MB for a forced pack on a public server, and under 100 MB only when the pack is optional. Above that, first joins get slow and mobile Bedrock clients struggle.

Does PackSquash make resource packs smaller?

Yes. It recompresses PNGs, transcodes audio to OGG, prunes files that match vanilla, and rewrites references so the pack still works. Run it in CI so every build is optimized the same way.

Why do my textures bleed or flicker at a distance?

Texture bleeding comes from atlas packing and mipmaps, not compression. Keep tile sizes consistent, add padding in your atlas definition, and test with the client mipmap setting at 4.

Does a large resource pack lag the server?

Rarely. The pack is processed on the client and delivered over HTTP, so server tick time is mostly unaffected. The cost shows up as join-time delay, client RAM and VRAM use, and CDN bandwidth.

Start With One Measurement

You do not need a full rewrite to fix a slow pack. Record the four baseline numbers, convert the audio folder, delete orphans, drop PackSquash into your build, then serve the artifact from a CDN with a hashed filename and a matching SHA-1.

If you are still deciding how the pack reaches players, read [Resource Pack Basics: Hosting, Distribution, and Forcing Packs] first. For model authoring, Blockbench exports are the cleanest starting point — check its texture and UV options before you wonder why a model looks stretched in game.

Generative AI was used to research and add structure to the original content so I can inform you as best as possible. All content has been reviewed by me.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

Search

Minecraft Server Tips and Tricks