GuidesFiles, photos and homeFiles with Nextcloud

Run Nextcloud with rootless Podman

Your own Nextcloud for files, calendars and contacts on Debian 13, in rootless Podman containers under a user of its own, with HTTPS, background jobs, updates and backups.

Tested on Nextcloud 34 and 35 on Debian 13 (trixie) on a Melonslab server Updated September 25, 2026

Recommended server for this guide

VC-S Micro · 2 vCPU · 8 GB Memory · 250 GB Storage

Month to month, no lock-in 7-day money-back guarantee

€7.99/mo

Deploy now
On this page

What you will set up

Nextcloud keeps your files, calendars and contacts on your own server, and syncs them to your computers and phones. Here it runs as three containers in one pod, under a user of its own called nextcloud:

  • Nextcloud itself, with Apache and PHP,
  • MariaDB, its database,
  • Redis, for caching and file locking.

Caddy, set up in the Podman guide, handles HTTPS in front of it.

Every step below was run on a fresh Melonslab VC-P Alloy (2 vCPU, 8 GB) with Debian 13: Nextcloud 34 was installed, then upgraded to 35. At idle, the containers used under 300 MB of memory. Files, calendar and contact access, HTTPS over IPv4 and IPv6, visitors' real IP addresses, background jobs, a reboot and the backup were all checked.

Before you start

You need:

  • a server set up as in the Podman guide, with Caddy running;
  • a name for your Nextcloud, such as cloud.example.com, with an A record and an AAAA record pointing at your server.

The examples use cloud.example.com for the name and 203.0.113.10 for your server's IPv4 address. Replace them with your own throughout.

For Nextcloud, our Standard plans are usually the better fit: they have hard disks in RAID 10, from 250 GB on the smallest plan up to 1 TB, so there is room for files at a lower price. The Performance plans have faster SSD storage but less of it.

1. Create the user

As root:

useradd -m -s /bin/bash nextcloud
loginctl enable-linger nextcloud
machinectl shell nextcloud@

Everything up to step 6 runs as nextcloud.

2. Create the passwords

printf %s "$(openssl rand -hex 24)" | podman secret create nextcloud-db-password -
printf %s "$(openssl rand -hex 16)" | podman secret create nextcloud-admin-password -

Podman stores these as secrets and hands them to the containers when they start, so no password is written into the files below. printf %s keeps a line break out of the password.

3. Describe the pod

mkdir -p ~/.config/containers/systemd

Create ~/.config/containers/systemd/nextcloud.pod:

[Pod]
PodName=nextcloud
# Only Caddy, on this server, can reach Nextcloud: the port is not open to the internet.
PublishPort=127.0.0.1:8081:80
AddHost=cloud.example.com:host-gateway

[Install]
WantedBy=default.target

The containers in a pod share one network and reach each other at 127.0.0.1. The only way in is port 8081 on the server's own loopback address, where Caddy connects, so neither Nextcloud nor its database can be reached from the internet. AddHost lets Nextcloud reach its own public address, which it uses to check its setup.

4. Add the database and cache

Create ~/.config/containers/systemd/nextcloud-db.container:

[Container]
ContainerName=nextcloud-db
Image=docker.io/library/mariadb:11.8
Pod=nextcloud.pod
Volume=nextcloud-db:/var/lib/mysql
Environment=MARIADB_DATABASE=nextcloud MARIADB_USER=nextcloud
Environment=MARIADB_RANDOM_ROOT_PASSWORD=1 MARIADB_AUTO_UPGRADE=1
Secret=nextcloud-db-password,type=env,target=MARIADB_PASSWORD
Exec=--transaction-isolation=READ-COMMITTED
AutoUpdate=registry

[Service]
Restart=always

MariaDB 11.8 is the version Nextcloud recommends, and READ-COMMITTED the setting it asks for. MARIADB_AUTO_UPGRADE updates the database files when a newer MariaDB image arrives.

Create ~/.config/containers/systemd/nextcloud-redis.container:

[Container]
ContainerName=nextcloud-redis
Image=docker.io/library/redis:8-alpine
Pod=nextcloud.pod
AutoUpdate=registry

[Service]
Restart=always

5. Add Nextcloud

Create ~/.config/containers/systemd/nextcloud-app.container:

[Unit]
After=nextcloud-db.service nextcloud-redis.service

[Container]
ContainerName=nextcloud-app
Image=docker.io/library/nextcloud:34-apache
Pod=nextcloud.pod
Volume=nextcloud:/var/www/html
Environment=MYSQL_HOST=127.0.0.1 MYSQL_DATABASE=nextcloud MYSQL_USER=nextcloud
Secret=nextcloud-db-password,type=env,target=MYSQL_PASSWORD
Environment=NEXTCLOUD_ADMIN_USER=admin
Secret=nextcloud-admin-password,type=env,target=NEXTCLOUD_ADMIN_PASSWORD
Environment=NEXTCLOUD_TRUSTED_DOMAINS=cloud.example.com
Environment=OVERWRITEPROTOCOL=https OVERWRITECLIURL=https://cloud.example.com
Environment=TRUSTED_PROXIES=203.0.113.10
Environment=REDIS_HOST=127.0.0.1
AutoUpdate=registry

[Service]
Restart=always

On its first start, the container installs Nextcloud with the user admin. Caddy's connections reach the pod from the server's own IPv4 address, so TRUSTED_PROXIES names that address: Nextcloud then takes each visitor's real IP address from what Caddy passes on, which it needs to slow down password guessing. The two OVERWRITE settings make Nextcloud's own links use HTTPS.

Version 34 is the one the image marks as stable. With the 34-apache tag, updates within 34 install themselves; you move to the next major version yourself (step 10).

Start it, and follow the log:

systemctl --user daemon-reload
systemctl --user start nextcloud-pod
podman logs -f nextcloud-app

The first start downloads about 2 GB of images and installs Nextcloud, which takes a few minutes. When the log says Nextcloud was successfully installed and then shows Apache starting, press Ctrl+C.

6. Put Caddy in front

Go back to root with exit, and switch to Caddy's user:

machinectl shell caddy@

Add this block to the end of ~/Caddyfile:

cloud.example.com {
    reverse_proxy 127.0.0.1:8081
    redir /.well-known/carddav /remote.php/dav/ 301
    redir /.well-known/caldav /remote.php/dav/ 301
    header Strict-Transport-Security "max-age=15552000; includeSubDomains"
}

And restart Caddy, which briefly interrupts every site it serves:

systemctl --user restart caddy

Caddy gets a certificate for cloud.example.com and renews it by itself, and sends plain HTTP visitors on to HTTPS. The two redir lines let calendar and contacts apps find their way from just your server's name.

Go back to root with exit, and to Nextcloud's user with machinectl shell nextcloud@. Show the admin password:

podman secret inspect --showsecret --format '{{.SecretData}}' nextcloud-admin-password

Open https://cloud.example.com and log in as admin.

7. Run background jobs

Nextcloud has regular tasks, such as cleaning up and sending notifications, which should run every five minutes. Make typing its occ command shorter first:

echo "alias occ='podman exec -u www-data nextcloud-app php occ'" >> ~/.bashrc
source ~/.bashrc
mkdir -p ~/.config/systemd/user

Create ~/.config/systemd/user/nextcloud-cron.service:

[Unit]
Description=Nextcloud background jobs

[Service]
Type=oneshot
ExecStart=/usr/bin/podman exec -u www-data nextcloud-app php -f /var/www/html/cron.php

And ~/.config/systemd/user/nextcloud-cron.timer:

[Unit]
Description=Run Nextcloud background jobs every 5 minutes

[Timer]
OnCalendar=*:0/5

[Install]
WantedBy=timers.target

Switch it on and tell Nextcloud to rely on it:

systemctl --user daemon-reload
systemctl --user enable --now nextcloud-cron.timer
occ background:cron

8. Finish the setup

occ config:system:set maintenance_window_start --type=integer --value=1
occ config:system:set default_phone_region --value=SE
occ maintenance:repair --include-expensive

The first line runs the heavy daily tasks in the four hours from 01:00 UTC, the second reads phone numbers without a country code as Swedish (use your own country's code), and the third finishes a task a new install leaves for you.

Under Administration settings → Overview, what remains are suggestions. Two are worth doing: set up email under Basic settings so Nextcloud can send password resets (your own mail server works), and turn on two-factor authentication for your users.

9. Keep it up to date

Switch on Podman's daily updates for this user:

systemctl --user enable --now podman-auto-update.timer

That keeps all three containers current. When a newer Nextcloud 34 image arrives, the container upgrades Nextcloud when it restarts.

10. Move to a new major version

A new major version is a step you take yourself, one version at a time: Nextcloud cannot skip one. Back up first (step 11), then pause the background jobs so they do not run halfway through the upgrade, change the tag and restart:

systemctl --user stop nextcloud-cron.timer
sed -i 's/nextcloud:34-apache/nextcloud:35-apache/' ~/.config/containers/systemd/nextcloud-app.container
systemctl --user daemon-reload
systemctl --user restart nextcloud-app
podman logs -f nextcloud-app

When the log shows Update successful, press Ctrl+C and start the background jobs again:

systemctl --user start nextcloud-cron.timer

In our test of 35.0.1, the log also reported a missing table, oc_federated_invites, and the Overview page listed database suggestions including "Replica lag", on a server without replication. Nextcloud 35 removes that empty table on purpose, and the replica check does not apply to a single server, so neither needs action.

11. Back up

mkdir -p ~/backup
occ maintenance:mode --on
podman exec nextcloud-db sh -c 'mariadb-dump --single-transaction -u nextcloud -p"$MARIADB_PASSWORD" nextcloud' > ~/backup/nextcloud-db.sql
podman volume export nextcloud --output ~/backup/nextcloud-files.tar
occ maintenance:mode --off

This saves the database, and Nextcloud's files, settings and program in one archive. Nextcloud is unavailable while it runs. Copy ~/backup to another machine afterwards: a backup on the same server does not survive losing the server. It includes the database password, so keep it private.

Connect your devices

  • Files: install the Nextcloud app on your computer or phone and enter https://cloud.example.com.
  • Calendars and contacts: add a CalDAV or CardDAV account (DAVx⁵ on Android, the built-in accounts on iPhone and Mac) with https://cloud.example.com and your user name. The app finds your calendars and address books by itself.

Troubleshooting

The browser says "Access through untrusted domain". The address you used is not the one in NEXTCLOUD_TRUSTED_DOMAINS, which only takes effect at install. To add one later: occ config:system:set trusted_domains 1 --value=cloud.example.com.

There is no certificate, or the browser warns about it. Check that the A and AAAA records point at the server, then read Caddy's log as the caddy user with podman logs caddy.

Every login appears to come from your server's own address. TRUSTED_PROXIES in step 5 must be the server's IPv4 address, the one Caddy's connections arrive from.

The Overview page says the Strict-Transport-Security header is not set, although Caddy sets it. Nextcloud could not reach itself through Caddy to check. The AddHost line in step 3 must name your Nextcloud's address.

Something is not running. As nextcloud, podman ps lists the containers and systemctl --user status nextcloud-pod shows the pod.

Your files, in Sweden

With Nextcloud on a Melonslab server, your files, calendars and contacts are stored in Sweden, under Swedish and EU law, on a server you control.

Run it on your own server

VC-S Micro

€7.99/mo

vCPU
2
Memory
8 GB
Storage
250 GB
Transfer
10 TB
Standard
HDD · RAID 10
  • Full root access
  • Native /64 IPv6
  • RAID-protected storage
  • Malmö, Sweden
  • Month to month, no lock-in
  • 7-day money-back guarantee
All guides