Skip to content

How to Deploy Plex Media Server on a VPS

Updated Oct 2026

verified on Ubuntu 26.04 · Oct 2026
We earn commissions when you shop through the links below. Full disclosure →

Self-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.

Before you start
  • 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
Need a box for this guide? Kamatera's free tier lets you spin one up now.Start free on Kamatera → (opens in new tab)

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.

Where to host itaffiliate disclosure
Hetzner Cloudrun it on
2 vCPU · 4 GB RAM · 80 GB SSD · $23.59/mo
Get Hetzner Cloud (opens in new tab)
Kamaterafree trial
1 vCPU · 1 GB RAM · 20 GB SSD · $4.00/mo
Start free on Kamatera → (opens in new tab)
DigitalOceanalso works on
1 vCPU · 1 GB RAM · 25 GB SSD · $6.00/mo
Deploy on DigitalOcean → (opens in new tab)

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:

  1. When the wizard asks to add a library, point it at /data, the folder you mapped to /srv/media on the server. Put your media there (rsync, SFTP, or a cloud-sync tool) and Plex will scan it.
  2. 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. The ADVERTISE_IP setting in the Compose file already points apps at that address.
  3. 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.
  4. Create managed users or invite friends from your account instead of sharing your login. Review who has access to which libraries.
  5. 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.

Next steps

How to self-host Plex →More self-hosted media server tools →Best VPS for Plex & Media Servers →Automatic HTTPS with Caddy →Run Claude Code with Ollama on Your Own VPS →Deploy Coolify on a VPS →How to Deploy Actual Budget on a VPS →How to Deploy AFFiNE on a VPS →How to Deploy AnythingLLM on a VPS →How to Deploy Appwrite on a VPS →How to Deploy Audiobookshelf on a VPS →How to Deploy Authelia on a VPS →How to Deploy authentik on a VPS →How to Deploy Baserow on a VPS →How to Deploy Beszel on a VPS →How to Deploy Bitwarden on a VPS →How to Deploy BookStack on a VPS →How to Deploy CapRover on a VPS →How to Deploy Checkmate on a VPS →How to Deploy copyparty on a VPS →How to Deploy Directus on a VPS →How to Deploy Discourse on a VPS →How to Deploy docker-mailserver on a VPS →How to Deploy Docmost on a VPS →How to Deploy Dokku on a VPS →How to Deploy Dokploy on a VPS →How to Deploy Firefly III on a VPS →How to Deploy Flarum on a VPS →How to Deploy Forgejo on a VPS →How to Deploy Gatus on a VPS →How to Deploy Ghost on a VPS →How to Deploy Ghostfolio on a VPS →How to Deploy Gitea on a VPS →How to Deploy a GitHub Actions Self-Hosted Runner on a VPS →How to Deploy GitLab on a VPS →How to Deploy GlitchTip on a VPS →How to Deploy Grafana on a VPS →How to Deploy Graylog on a VPS →How to Deploy Headscale on a VPS →How to Deploy Healthchecks on a VPS →How to Deploy Home Assistant on a VPS →How to Deploy Immich on a VPS →How to Deploy Infisical on a VPS →How to Deploy Invoice Ninja on a VPS →How to Deploy Jan on a VPS →How to Deploy Jellyfin on a VPS →How to Deploy Kanboard on a VPS →How to Deploy Karakeep on a VPS →How to Deploy Keycloak on a VPS →How to Deploy Leantime on a VPS →How to Deploy LibreChat on a VPS →How to Deploy Linkwarden on a VPS →How to Deploy LocalAI on a VPS →How to Deploy Mailcow on a VPS →How to Deploy Mailu on a VPS →How to Deploy Matomo on a VPS →How to Deploy Mattermost on a VPS →How to Deploy Meilisearch on a VPS →How to Deploy Memos on a VPS →How to Deploy a Minecraft Server on a VPS →How to Deploy n8n on a VPS →How to Deploy Navidrome on a VPS →How to Deploy NetBird on a VPS →How to Deploy Netdata on a VPS →How to Deploy Nextcloud on a VPS →How to Deploy Next.js to a VPS →How to Deploy Nginx Proxy Manager on a VPS →How to Deploy NocoDB on a VPS →How to Deploy ntfy on a VPS →How to Deploy Odoo on a VPS →How to Deploy Ollama on a VPS →How to Deploy ONLYOFFICE Docs on a VPS →How to Deploy Open WebUI on a VPS →How to Deploy OpenHands on a VPS →How to Deploy OpenObserve on a VPS →How to Deploy OpenProject on a VPS →How to Deploy Outline on a VPS →How to Deploy Pangolin on a VPS →How to Deploy Paperless-ngx on a VPS →How to Deploy Passbolt on a VPS →How to Deploy Plane on a VPS →How to Deploy Plausible Analytics on a VPS →How to Deploy Pocket ID on a VPS →How to Deploy PocketBase on a VPS →How to Deploy Prometheus on a VPS →How to Deploy Psono on a VPS →How to Deploy Radarr on a VPS →How to Deploy Rocket.Chat on a VPS →How to Deploy SigNoz on a VPS →How to Deploy Sonarr on a VPS →How to Deploy Stalwart on a VPS →How to Deploy Stirling-PDF on a VPS →How to Deploy Supabase on a VPS →How to Deploy Synapse on a VPS →How to Deploy Taiga on a VPS →How to Deploy TeamPass on a VPS →How to Deploy Tinyauth on a VPS →How to Deploy Traefik on a VPS →How to Deploy Trilium on a VPS →How to Deploy Twenty CRM on a VPS →How to Deploy Umami on a VPS →How to Deploy Uptime Kuma on a VPS →How to Deploy Vaultwarden on a VPS →How to Deploy Vikunja on a VPS →How to Deploy wg-easy on a VPS →How to Deploy Wiki.js on a VPS →How to Deploy WordPress on a VPS →How to Deploy Zabbix on a VPS →How to Deploy Zitadel on a VPS →How to Deploy Zulip on a VPS →Docker & Compose on Ubuntu 26.04 →Building AI Workflows with n8n →Install Open WebUI with Ollama →Adding AI-Powered Insights to Plausible Analytics →Building AI-Powered Apps with Supabase and pgvector →

We use analytics cookies (Google Analytics, PostHog) to see which guides are useful. No ad networks, no cross-site tracking. See our privacy policy.