How to Deploy Plex Media Server on a VPS
Updated Oct 2026
verified on Ubuntu 26.04 · Oct 2026Self-host Plex Media Server on a VPS — run the official Docker image with host networking behind Caddy HTTPS, claim the server over an SSH tunnel, and set up a restore-tested backup and a pinned-image upgrade path.
- A VPS with 2 GB+ RAM and enough disk for your media (Plex's own guidance is 2 GB; transcoding wants real CPU, not just memory)
- A fresh Ubuntu 24.04 or 26.04 server with root/sudo SSH access
- A free Plex account, and a domain or subdomain you can point at the server
What Plex is
Plex Media Server catalogs your own movies, TV, music and photos and streams them to apps on nearly every phone, TV and browser, adding posters, metadata, watch history and per-user libraries. It is a proprietary product with a free tier and an optional paid Plex Pass for extras such as hardware transcoding, rated 2 / 5 to deploy. Unlike most apps in this catalog it is not open source, and it depends on a plex.tv account for sign-in and discovery even when the media never leaves your server.
This guide runs the official Docker image with Docker Compose and Caddy in front for automatic HTTPS. It is honest about one limitation: claiming the server to your Plex account needs a one-time browser step that cannot be scripted, so the guide uses an SSH tunnel for it. It picks up from "you have a server" and ends with a Plex server on HTTPS, a restore-tested backup and an upgrade routine. Plex also has its own page in the VPS rankings: Best VPS for Plex. For general help choosing a provider, see Best VPS for self-hosting.
A word on what a VPS is good for here. Streaming from a remote server is bandwidth-heavy, and a server with few CPU cores cannot transcode many streams at once. It works well for direct-play libraries of files your clients can play natively, and poorly for 4K transcoding on a cheap plan. Check the bandwidth allowance of any plan before you move a big library onto it.
Server sizing
On our test box the Plex container idled at roughly 164 MB of RAM with an empty library, and the image plus a fresh configuration used about 516 MB of disk. That is the idle floor: 2 GB of RAM is the practical minimum once you scan a library, and transcoding raises CPU use sharply, so more vCPUs matter more than more RAM. Media is the real disk cost, and the metadata and thumbnails Plex generates add to it, so size the disk for the library plus a healthy margin, or attach block storage.
Prepare the server
Update the system and install the few tools this guide uses:
sudo apt-get update && sudo apt-get -y upgrade
sudo apt-get install -y curl openssl ufw dnsutils
If you're logged in as root, create a non-root sudo user and continue as that user:
adduser deploy
usermod -aG sudo deploy
Then open only SSH and the two ports the reverse proxy needs. Plex's own port
32400 stays closed to the internet:
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw --force enable
The Plex container uses host networking, as the official image recommends,
because Plex's local discovery and DLNA features rely on it. Host-networked
containers are ordinary host sockets, so ufw does filter them. With only 22,
80 and 443 allowed, port 32400 is unreachable from outside, and Caddy
becomes the only public door.
Paid link — we earn a commission if you shop through it.
Install Docker
Plex is installed with Docker Compose. If Docker isn't on the server yet, the convenience script installs Docker Engine and the Compose plugin (the Docker and Compose on Ubuntu guide covers the details):
command -v docker >/dev/null || curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker "$USER"
sudo docker compose version
The group change applies on your next login; until then the commands below use
sudo docker.
Install Plex
Create the project directory, the folder Plex keeps its configuration in, and a
folder for your media. The container runs as user ID 1000 (set below), so that
user must own both. Set your hostname first (this guide uses
plex.example.com; replace it with yours). The [ -f .env ] guard keeps the
step safe to re-run:
mkdir -p ~/plex/config && cd ~/plex
sudo mkdir -p /srv/media && sudo chown 1000:1000 /srv/media ~/plex/config
DOMAIN=plex.example.com
[ -f .env ] || printf 'DOMAIN=%s\n' "$DOMAIN" > .env
cat .env
Now the Compose file. It runs the official plexinc/pms-docker image with the
version tag pinned, so a Plex update is a deliberate edit and not a surprise
restart. ADVERTISE_IP tells Plex the public HTTPS address to hand to apps.
Caddy also uses host networking so it can reach Plex on 127.0.0.1:
cd ~/plex
cat > docker-compose.yml <<'EOF'
services:
plex:
image: plexinc/pms-docker:1.43.4.10903-e5521bd8c
restart: unless-stopped
network_mode: host
environment:
TZ: UTC
PLEX_UID: "1000"
PLEX_GID: "1000"
ADVERTISE_IP: "https://${DOMAIN}:443"
PLEX_CLAIM: ${PLEX_CLAIM:-}
volumes: ["./config:/config", "/srv/media:/data"]
caddy:
image: caddy:2
restart: unless-stopped
network_mode: host
environment:
DOMAIN: ${DOMAIN}
volumes: ["./Caddyfile:/etc/caddy/Caddyfile:ro", "caddy_data:/data"]
depends_on: [plex]
volumes:
caddy_data:
EOF
cat > Caddyfile <<'EOF'
{$DOMAIN} {
reverse_proxy 127.0.0.1:32400
}
EOF
If you already have a Plex claim token (they come from
https://plex.tv/claim and expire in four minutes), you can add it before the
first start and skip the tunnel step below:
echo "PLEX_CLAIM=claim-xxxxxxxxxxxxxxxxxxxx" >> ~/plex/.env
Start it and wait for Plex to answer. The /identity endpoint needs no
sign-in and returns the server's machine identifier:
cd ~/plex
sudo docker compose up -d
for i in $(seq 1 40); do
code=$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:32400/identity)
[ "$code" = "200" ] && break
sleep 3
done
echo "Plex answered HTTP $code"
curl -s http://127.0.0.1:32400/identity; echo
sudo docker compose ps
HTTP 200 and an identity response with claimed="0" mean the server is up
and not yet linked to any account.
HTTPS + domain
Create an A record for your Plex hostname (say plex.example.com)
pointing at the server's public IP, and confirm it resolves before Caddy tries
to get a certificate:
dig +short plex.example.com
Caddy is already running. It requests the certificate as soon as the name resolves (the loop below waits for it) and renews it on its own (see Automatic HTTPS with Caddy for how that works). Check that HTTPS answers:
. ~/plex/.env
for i in $(seq 1 20); do
curl -s -o /dev/null "https://$DOMAIN/identity" && break
sleep 5
done
curl -s -o /dev/null -w 'HTTPS status %{http_code}, certificate check %{ssl_verify_result}\n' \
"https://$DOMAIN/identity"
A ssl_verify_result of 0 means the certificate verified. Port 32400 is
not open on the firewall, so http://your-server-ip:32400 must not load from
your laptop. That is the intended setup.
Claim the server and first login
A new Plex server has to be linked to your Plex account ("claimed"). Plex
allows the claim only from the same machine, so from your own computer open an
SSH tunnel to the server (replace deploy and the address with yours):
ssh -L 32400:127.0.0.1:32400 deploy@your-server-ip
With the tunnel running, browse to http://localhost:32400/web on your
computer, sign in with your Plex account, and name the server. Then:
- When the wizard asks to add a library, point it at
/data, the folder you mapped to/srv/mediaon the server. Put your media there (rsync, SFTP, or a cloud-sync tool) and Plex will scan it. - Open Settings → Remote Access. Because Caddy fronts the server, leave
Plex's own port mapping alone and set Settings → Network → Custom server
access URLs to
https://plex.example.com:443. TheADVERTISE_IPsetting in the Compose file already points apps at that address. - Under Settings → Network, set the secure connections option to "Preferred", not "Required", until you have confirmed that every client you care about connects through the HTTPS address.
- Create managed users or invite friends from your account instead of sharing your login. Review who has access to which libraries.
- Close the tunnel when you're done. Day to day, clients use the HTTPS address.
Backups
Plex keeps its entire state in the config folder: the library database,
metadata, thumbnails and preferences. Your media in /srv/media is separate and
should be backed up on its own schedule. The database is a live SQLite file, so
stop Plex briefly for a consistent copy:
cd ~/plex
sudo docker compose stop plex
sudo tar czf ~/plex-config-$(date +%F).tar.gz -C ~/plex config
sudo docker compose start plex
sudo tar czf ~/plex-compose-$(date +%F).tar.gz docker-compose.yml Caddyfile .env
sudo chmod 600 ~/plex-config-*.tar.gz ~/plex-compose-*.tar.gz
ls -lh ~/plex-config-*.tar.gz ~/plex-compose-*.tar.gz
The config folder can grow large because of thumbnails and metadata, but it
is the part that holds your watch history, users and library layout. Copy both
archives off the server, to object storage, another machine, anywhere that
survives this VPS dying, and run the sequence from cron during a quiet hour.
The archives hold your account token and .env, so the commands above also
restrict them to their owner. As a quick check, unpack the config archive into a
scratch folder and confirm it contains Plex's configuration (this does not start
a server from it):
mkdir -p /tmp/plex-restore && cd /tmp/plex-restore
sudo tar xzf ~/plex-config-$(date +%F).tar.gz
sudo test -f "/tmp/plex-restore/config/Library/Application Support/Plex Media Server/Preferences.xml" && echo "Preferences.xml restored"
sudo rm -rf /tmp/plex-restore
On a new server, install Docker, unpack the compose archive into ~/plex,
unpack the config archive beside it, make sure user ID 1000 owns config and
your media folder, and run sudo docker compose up -d. Plex comes back as the
same server, with the same library and users.
What this guide's test run did not cover: whether ufw filters the
host-networked port 32400 (it relies on documented ufw behaviour), and the
claim flow, library scan and playback, which need a Plex account and real media.
Upgrades
Because the image tag is pinned, upgrading Plex is an edit. Check Plex's
release notes and the tags on Docker Hub, take a backup (previous section),
change the tag in docker-compose.yml, then pull and recreate:
cd ~/plex
sudo docker compose pull
sudo docker compose up -d
sudo docker compose logs --tail 20 plex
Plex migrates its own database on the first start of a new version. That migration is one-way, which is why the backup comes first, and why you should not roll back to an older tag against a configuration a newer version has already opened: restore the backup if you need to go back.
Troubleshooting
The web UI says "Not authorized", or the setup wizard does not load. An
unclaimed server only accepts setup from the machine itself. Use the SSH tunnel
from the claim step and open http://localhost:32400/web there, not the public
address.
Clients can't connect, or they fall back to a slow relay. Check that the
custom server access URL is set to your HTTPS address, that the certificate is
valid, and that the hostname resolves to the server. Run
sudo docker compose logs plex for connection errors.
Caddy never gets a certificate. Confirm dig +short plex.example.com
returns the server's IP and that your provider's network-level firewall, not
just ufw, allows inbound 80 and 443. A blocked port 80 silently breaks the
ACME challenge. Read Caddy's side with sudo docker compose logs caddy.
The library does not see new files. Plex runs as user ID 1000, so that user
must be able to read the media: sudo chown -R 1000:1000 /srv/media or make the
files group-readable. Then trigger a scan from the library's menu.
Playback stutters on a transcode. The server is out of CPU. Prefer clients that can direct-play your file formats, lower the stream quality for remote clients, or move to a plan with more vCPUs. Hardware transcoding needs a GPU, which most VPS plans don't have.
Verification + next steps
Not exercised in our test run: the new-user step and the DNS check against your real domain (we ran on a throwaway box with a temporary hostname); claiming the server with a real Plex account, library scans, remote playback and the ufw filtering of the host-networked port.
You're done when https://plex.example.com/identity answers with a valid
certificate, you have claimed the server and it appears in your Plex account,
port 32400 is unreachable from outside, a client can play a file from your
library over the HTTPS address, docker compose ps shows both services up
after a reboot (restart: unless-stopped brings them back), and you have
unpacked a config backup into a scratch folder at least once.
From there, set up your libraries and metadata agents, invite users, and think about how media gets onto the server. For the ranked server picks and the app's catalog details, see Best VPS for Plex, Best VPS for self-hosting and the Plex entry.