Turning a ThinkPad X270 into a Self-Hosted Nextcloud Server (Docker + Nextcloud AIO + Tailscale, Behind CGNAT)
Repurpose an old ThinkPad X270 into an always-on, self-hosted Nextcloud server using Docker, Nextcloud AIO, and Tailscale — with secure remote access from anywhere, no open ports, even behind CGNAT.
Last verified September 2026 against Ubuntu Server 26.04 LTS. Software versions and exact commands change over time — if something here doesn't match what you see on screen, check the linked official docs before troubleshooting blindly.
Why repurpose an old laptop for this?
Before the technical build, it's worth saying why an old ThinkPad is such a good fit for a job like this — and why the X270 in particular is almost purpose-built for it.
A self-hosted Nextcloud instance for one person is a genuinely light workload. It sits idle most of the day, wakes up when your phone uploads a photo or you open a document, and goes back to sipping power. Throwing a brand-new mini PC or a Raspberry Pi with a pile of accessories at it is overkill when a laptop you already own — one that's been sitting in a drawer since 2019 — has more than enough CPU, four times the RAM of a Pi, and, crucially, a real NVMe SSD instead of an SD card that wears out.
A laptop also solves problems a Pi or mini PC makes you solve yourself. It has a built-in screen and keyboard for the initial install and for the inevitable "why won't it boot" moment. It runs near-silent. It sips power (the X270's 7th-gen U-series chip idles at just a few watts). And it comes with something no other form factor gives you for free: a battery.
The X270's dual battery is a built-in UPS
This is the standout reason the X270 specifically is a great pick, and it's worth understanding.
The X270 uses Lenovo's Power Bridge design — it has two batteries: a small internal one soldered inside, and a removable external one that clips into the back. The point of the design, originally, was hot-swapping: you could pull and replace the external battery without shutting the laptop down, because the internal battery keeps it alive during the swap.
For a home server, that same design turns the laptop into its own uninterruptible power supply. A desktop, a mini PC, or a Raspberry Pi all drop dead the instant your power flickers — and residential power flickers more than people realize: a brief brownout, someone tripping a breaker, the utility switching feeds. Each of those is a hard, unclean shutdown for a normal always-on server, and hard shutdowns are exactly what corrupt databases and filesystems. Nextcloud runs a real database (PostgreSQL); an unclean power cut mid-write is the classic way to end up with a corrupted instance.
With the X270, a power blip is a non-event. The two batteries carry the machine through short outages entirely, and through longer ones they give you a window to shut down cleanly. You get UPS behavior — the single most recommended add-on for any home server — with zero extra hardware, zero extra cost, and zero extra cabling. On a desktop-based server, a UPS that does the same job is a $60–150 purchase that takes up floor space.
If your X270's removable battery still holds a decent charge, you effectively have a multi-hour UPS for free. Even a degraded battery that only holds 20% of its original capacity is still worth keeping in place — 20% of an X270 battery is more than enough to ride out the seconds-to- minutes outages that cause most filesystem corruption.
The one trade-off: keeping a battery at 100% charge while permanently plugged in does wear it down faster over months. Part 4 covers setting a charge threshold so the battery stays in a healthier middle range while still giving you that UPS cushion — the best of both worlds.
What this guide builds
An old ThinkPad X270 (Core i7-7500U, 2 cores/4 threads, 16GB RAM, 256GB NVMe) running:
- Ubuntu Server 26.04 LTS ("Resolute Raccoon") as the base OS
- Docker Engine + Compose to run everything in containers
- Nextcloud All-in-One (AIO) — the official, easiest way to run a full-featured Nextcloud instance, with Collabora Online (the self-hosted office suite) enabled, and Talk left disabled since this is a single-user instance
- Tailscale to give you secure remote access without opening a single port on your router — necessary since T-Mobile Home Internet sits behind CGNAT and you can't forward ports at all
The end result: you can reach your files, calendar, contacts, photos, and edit documents/spreadsheets/slides in-browser, from any device on your Tailscale network (phone, laptop, tablet) from anywhere in the world, with a real HTTPS certificate and no public exposure of your home network.
This setup is also tailored to a specific usage pattern worth calling out: a single-user instance used as a "dropbox" — phone photos and documents land here first, then you move them to your NAS at your leisure. That means data on this box is meant to be transient rather than a permanent archive, which simplifies the storage layout decisions below.
Is this hardware actually good enough?
Short answer: yes, easily. Longer answer:
Nextcloud AIO's own minimum requirements (once you enable any optional container like Talk or Collabora) are a dual-core CPU, 2GB RAM, and 40GB of storage. The X270 has 8x the RAM and a far stronger CPU than plenty of people's Raspberry Pi-based or low-end VPS Nextcloud setups, except with an actual NVMe SSD instead of an SD card. For a single user syncing files, photos, contacts, calendars, and editing documents in Collabora, this machine is comfortably overpowered — Collabora's CPU cost scales with how many documents are open simultaneously, not with total usage, and as a solo user you'll basically never have more than one or two open at once.
Where it would struggle is Nextcloud Talk with several simultaneous video participants, which is exactly why we're leaving Talk disabled — you don't need it, and skipping it frees up RAM for everything else.
Storage is simpler than it might look, too, given how you're planning to use this. Since this box is a transient landing spot — phone photos and documents pass through it before you move them to your NAS — you don't need to reserve a large permanent pool of space, and the 256GB internal NVMe is genuinely plenty. This guide stores Nextcloud's data directory directly on the internal drive rather than adding an external one, which keeps the hardware list short. The one thing worth doing is keeping half an eye on free space (covered in the troubleshooting section) so you don't fill the drive if files pile up for longer than expected between transfers to the NAS.
Architecture at a glance
Your phone/laptop (Tailscale app)
|
| encrypted WireGuard tunnel via Tailscale
v
ThinkPad X270 (Ubuntu Server, always on)
+- tailscaled > terminates the tailnet connection, owns the
| *.ts.net HTTPS certificate, "tailscale serve"
| bridges that HTTPS to a local port
+- Docker Engine
| +- Nextcloud AIO mastercontainer
| +- spawns: Nextcloud app, Apache, PostgreSQL,
| Redis, Borg backup, Collabora Online (office suite)
+- Internal NVMe > a dedicated directory holds the actual
Nextcloud data (transient "dropbox" storage)You never forward a port on your T-Mobile gateway. Tailscale's relay/NAT-
traversal infrastructure handles reachability, and tailscale serve gets
you a trusted certificate without needing a public domain at all.
Prerequisites checklist
- ThinkPad X270, charger, and (ideally) a wired Ethernet connection — the X270 has a built-in RJ45 port on the left side. Wired beats Wi-Fi for a server that needs to stay reachable.
- A USB drive (8GB+) to create the Ubuntu Server installer
- A second computer to create that installer and to SSH into the X270 afterward
- A free Tailscale account (the free tier covers up to 100 devices / 3 users, more than enough for personal use)
- About 2 hours for the initial setup, plus patience for downloads
Part 1 — Prepare the laptop physically and in BIOS
Before installing anything, set a few things up so the laptop behaves like a server, not a laptop that occasionally suspends on you.
- Clean it. If this machine has been sitting in a drawer, blow out the fan and heatsink vents with compressed air. A server that runs 24/7 needs working cooling far more than a laptop you use for an hour at a time.
- Enter the BIOS (tap
EnterthenF1at boot, orF1directly depending on firmware version). - Set "Power On after Power Failure" / "AC Detect" behavior to ON. This is usually under Config → Power. This makes the laptop automatically boot back up after a power outage instead of sitting there off until you physically press the power button. Critical for an unattended server.
- Disable wake-on-lid / wake triggers you don't need, and leave Wake on LAN off unless you specifically want it.
- If your unit has battery charge-threshold settings in BIOS
(Config → Power → Battery), consider setting a conservative charge
threshold (e.g., start charging at 40-45%, stop at 55-60%) rather than
always topping up to 100%. A laptop battery that sits at 100% charge
while plugged in 24/7 degrades faster than one kept in a moderate range.
If your BIOS doesn't expose this, you can do it in software later with
tpacpi-bat(covered in Part 4). - Save and exit BIOS.
Don't remove the external battery to "save wear," even though it's tempting. As covered in the intro, that battery is your free UPS — the charge threshold in step 5 (or Part 4) is the right way to manage wear while keeping the outage protection.
Part 2 — Install Ubuntu Server 26.04 LTS ("Resolute Raccoon")
- On another computer, download Ubuntu Server 26.04 LTS from ubuntu.com/download/server. This is the newest LTS release (out since April 2026), supported with security updates for years to come, and Docker, Tailscale, and every other tool in this guide already have full native support for it.
- Flash it to a USB drive. On Linux/macOS you can use
dd; on any OS, Rufus (Windows) or balenaEtcher (cross-platform) are simpler. - Plug the USB drive into the X270, boot it, and tap
F12at the Lenovo logo to get the boot menu, then select the USB drive. - Walk through the Ubuntu Server installer:
- Choose your language and keyboard layout.
- For networking, if you have it plugged into Ethernet, it'll just work via DHCP. If you're stuck on Wi-Fi for now, configure it here (though I'd still strongly recommend moving to wired Ethernet for the actual server deployment).
- Storage: use the entire 256GB NVMe drive with the default LVM partitioning the installer suggests. Don't overthink this — simplicity here makes life easier later.
- Profile setup: pick a username and a strong password. Write the
username down; you'll need it for every
sshcommand later. - SSH setup: when prompted, check "Install OpenSSH server." This is what lets you administer the machine headless after the initial install — you don't want to be plugging in a keyboard and monitor to this thing once it's tucked away somewhere.
- Skip the "featured server snaps" screen (you don't need any of them — Docker will be installed manually in Part 5).
- Let it finish, reboot, and remove the USB drive when prompted.
- After reboot, log in once at the console just to confirm everything came
up, then note the laptop's local IP address (printed on the login
screen, or run
ip a).
From here on, do everything else over SSH from your other computer — it's much easier to copy-paste commands that way:
ssh your-username@192.168.x.xPart 3 — Initial server setup and hardening
These steps apply to basically any headless Ubuntu server, laptop or not.
3.1 Update everything
sudo apt update && sudo apt upgrade -y
sudo rebootSSH back in after it reboots.
3.2 Set a static local IP (optional but recommended)
A static IP on your home network makes SSH and troubleshooting easier later, even though Tailscale will be your main access method once it's set up. Check your router's DHCP reservation feature (look for "DHCP reservations" or "static leases" in your router's admin page) and reserve an IP for the X270's MAC address — this is usually simpler than editing netplan configs directly, and survives Ubuntu upgrades cleanly.
3.3 Create a swap file
The X270 has 16GB of RAM, which is plenty, but a small swap file is cheap insurance against an out-of-memory crash if a container misbehaves:
sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab3.4 Enable automatic security updates
sudo apt install unattended-upgrades -y
sudo dpkg-reconfigure --priority=low unattended-upgradesChoose "Yes" when prompted. This keeps the OS itself patched without you needing to remember to do it (this does not auto-update your Docker containers — that's handled separately by AIO's own updater, covered in Part 12).
3.5 Basic firewall
Ubuntu ships ufw (Uncomplicated Firewall). Set a sane default and allow
only SSH for now — Tailscale's own interface doesn't need any inbound
firewall rules opened, since it tunnels through outbound connections:
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw enableConfirm it didn't lock you out: sudo ufw status. You should see SSH
allowed and nothing else.
3.6 (Recommended) Harden SSH a little
If you're comfortable with SSH key-based login, set that up now and disable password authentication:
# on your OTHER computer, if you don't already have a key:
ssh-keygen -t ed25519
ssh-copy-id your-username@192.168.x.xThen on the X270:
sudo sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo systemctl restart sshTest that you can still log in with your key in a new terminal window before closing your current session — you don't want to lock yourself out of a headless machine.
Part 4 — Make the OS behave like a server, not a laptop
This is the part that's specific to using a laptop chassis as a headless server. Skip none of this, or you'll find your "server" asleep the next time you try to connect.
4.1 Stop the laptop from suspending, ever
sudo systemctl mask sleep.target suspend.target hibernate.target hybrid-sleep.targetThis prevents systemd from ever putting the machine to sleep, regardless of what triggers it (idle timeout, lid switch, low battery, etc.).
4.2 Ignore the lid switch entirely
Edit the logind config:
sudo nano /etc/systemd/logind.confFind (or add) these lines and set them like this:
HandleLidSwitch=ignore
HandleLidSwitchExternalPower=ignore
HandleLidSwitchDocked=ignoreSave, exit, then restart the service:
sudo systemctl restart systemd-logindNow you can close the lid and tuck the laptop away somewhere without it going to sleep.
4.3 Disable Wi-Fi power saving (if you're using Wi-Fi)
Skip this if you're on wired Ethernet (recommended). If you must use Wi-Fi,
power-saving can cause intermittent dropouts that are a pain to diagnose on
a headless box. On modern Ubuntu, the old iwconfig tool (from
wireless-tools) is no longer installed by default, so set this through
NetworkManager instead:
# Find your Wi-Fi device and connection name
nmcli device status
# Turn off power saving for the Wi-Fi connection (persists across reboots)
sudo nmcli connection modify "<your-wifi-connection-name>" \
802-11-wireless.powersave 2
sudo nmcli connection down "<your-wifi-connection-name>"
sudo nmcli connection up "<your-wifi-connection-name>"(A powersave value of 2 means "disabled." 3 means "enabled.") Wired
Ethernet avoids this whole problem — genuinely, just use the RJ45 port if
you possibly can.
4.4 Battery charge threshold (optional, prolongs battery life)
This is where you get the UPS benefit from the intro without cooking the
battery over time. If your BIOS didn't expose charge thresholds in Part 1,
you can manage it in Linux with tpacpi-bat (works on most ThinkPads with
the thinkpad_acpi kernel module, which is loaded by default on Ubuntu):
git clone https://github.com/teleshoes/tpacpi-bat.git
cd tpacpi-bat
sudo cp tpacpi-bat /usr/local/bin/
sudo tpacpi-bat -s ST 1 40 # start charging at 40%
sudo tpacpi-bat -s SP 1 60 # stop charging at 60%A 40–60% window keeps the battery healthy while still leaving you well over half a charge of runtime to ride out power blips and shut down cleanly during longer outages. If you value maximum UPS runtime over battery longevity, raise the stop threshold (e.g. 80%) — it's a direct trade-off between the two.
The X270's Power Bridge means these thresholds apply to both the internal
and external batteries. You can check both with tpacpi-bat -g ST 1 and
tpacpi-bat -g ST 2 (battery 1 is typically the external/removable pack,
battery 2 the internal).
4.5 Keep an eye on temperatures
sudo apt install lm-sensors -y
sudo sensors-detect # answer "yes" to the prompts
sensorsRun sensors occasionally for the first few days to make sure nothing is
running hotter than expected, especially if you're not 100% sure the
fan/heatsink are clean.
Part 5 — Install Docker Engine and the Compose plugin
Use Docker's official repository rather than the Ubuntu-packaged
docker.io, since it stays current and includes the Compose plugin.
# Remove any old/conflicting packages
sudo apt remove docker docker-engine docker.io containerd runc -y
# Set up Docker's official GPG key and repo
sudo apt update
sudo apt install ca-certificates curl -y
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Install Docker Engine + Compose plugin
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -yLet your user run Docker without typing sudo every time:
sudo usermod -aG docker $USERLog out and back in (or just close and reopen your SSH session) for the group change to take effect. Confirm it works:
docker run hello-world
docker compose versionOn a fresh Ubuntu 26.04 release, $VERSION_CODENAME may occasionally be
newer than what Docker's repo has published a component for. If
apt update complains that the Docker repo has no release file for your
codename, temporarily substitute the previous LTS codename (e.g.
noble) in the echo block above — Docker's packages are compatible,
and this is a common early-release gap that resolves once Docker
publishes the matching component.
Part 6 — Create a data directory on the internal NVMe
Since this instance is a transient landing spot for phone files and documents rather than long-term storage, there's no need for a separate external drive — the internal 256GB NVMe has plenty of headroom. Just create a dedicated directory for Nextcloud's data outside of Docker's own storage area, so it's easy to find, back up, or inspect directly:
sudo mkdir -p /var/lib/nextcloud-dataNextcloud AIO expects its data directory to exist but be empty on
first run, and it sets up the correct ownership itself during
initialization. Do not pre-chown this directory to 33:33 (or
anything else) beforehand — a common older-guide step that now trips
AIO's first-start sanity check. Just create the empty directory and let
AIO handle ownership.
Check how much free space you're working with whenever you want a sanity check:
df -h /Since files here are meant to pass through rather than pile up permanently, it's worth glancing at this output every so often, especially if you go a while without transferring things over to your NAS — see the troubleshooting section near the end for a quick way to keep an eye on this without having to remember to check manually.
Part 7 — Install Tailscale
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale upThis prints a URL — open it in a browser on any device where you're logged into your Tailscale account, and approve the new machine. Give it a recognizable hostname when prompted, or set one explicitly:
sudo tailscale up --hostname=nextcloud-x270Once connected, confirm it's online:
tailscale statusYou should see your X270 listed with a 100.x.y.z Tailscale IP. Note the
MagicDNS name too — it'll look like nextcloud-x270.tailxxxxx.ts.net.
You'll need this exact name in a moment.
7.1 Enable HTTPS certificates for your tailnet
This is required for the next part to work. In your browser:
- Go to the Tailscale admin console.
- Under DNS, confirm MagicDNS is enabled (it usually is by default).
- Under DNS → HTTPS Certificates (naming varies slightly by console version), enable "HTTPS Certificates."
This lets your tailnet automatically issue a real, browser-trusted Let's
Encrypt certificate for your device's *.ts.net name — entirely privately,
with no public DNS record and no open ports anywhere.
Part 8 — Deploy Nextcloud AIO with Docker Compose, bridged through Tailscale Serve
This is the part that solves the CGNAT problem properly. Nextcloud AIO
normally wants to manage its own HTTPS certificate for a domain that's
publicly reachable on ports 80/443 — which is exactly what you can't do
behind CGNAT. Instead, we tell AIO to listen on a private local port with
no certificate of its own, skip its domain check, and let tailscale serve
bridge your tailnet's already-valid HTTPS certificate to that port.
8.1 Create the Compose file
mkdir -p ~/nextcloud-aio
cd ~/nextcloud-aio
nano compose.yamlPaste this in (this is adapted from Nextcloud's own official Compose example, with the reverse-proxy settings needed for the Tailscale Serve approach):
services:
nextcloud-aio-mastercontainer:
image: ghcr.io/nextcloud-releases/all-in-one:latest
init: true
restart: always
container_name: nextcloud-aio-mastercontainer
ports:
- "8080:8080"
environment:
APACHE_PORT: "11000"
APACHE_IP_BINDING: "127.0.0.1"
SKIP_DOMAIN_VALIDATION: "true"
NEXTCLOUD_DATADIR: "/var/lib/nextcloud-data"
volumes:
- nextcloud_aio_mastercontainer:/mnt/docker-aio-config
- /var/run/docker.sock:/var/run/docker.sock:ro
volumes:
nextcloud_aio_mastercontainer:
name: nextcloud_aio_mastercontainerA quick explanation of the non-default lines, since getting these wrong is the #1 cause of confusion in every "AIO behind Tailscale" thread:
APACHE_PORT: "11000"— instead of AIO's Apache container trying to grab ports 80/443/8443 directly, it listens internally on port 11000.tailscale servewill be the thing that exposes this to your tailnet.APACHE_IP_BINDING: "127.0.0.1"— locks that port 11000 to localhost only, so it's never reachable from your LAN or the internet directly, only via the Tailscale bridge.SKIP_DOMAIN_VALIDATION: "true"— tells AIO not to bother checking that a domain resolves to a public IP on port 443, since you don't have one and don't want one. You'll still enter your.ts.netname in the setup wizard; it just won't be validated by AIO itself (Tailscale is the one actually proving the connection works).NEXTCLOUD_DATADIR: "/var/lib/nextcloud-data"— points all of Nextcloud's actual file storage at the directory you created in Part 6, on the internal NVMe, rather than the default Docker volume buried deep in/var/lib/docker.
8.2 Start it
docker compose up -d
docker compose logs -fWatch the logs for a minute or two until things settle (Ctrl+C to stop
following). The mastercontainer itself starts very quickly; the rest of the
Nextcloud stack only spins up after you complete the setup wizard in the
next step.
8.3 Bridge it with Tailscale Serve
sudo tailscale serve --bg http://localhost:11000You should see output confirming it's available at your .ts.net address,
something like:
Available within your tailnet:
https://nextcloud-x270.tailxxxxx.ts.net/
|-- proxy http://localhost:11000
Serve started and running in the background.Because we used --bg, this configuration persists across reboots and
Tailscale restarts automatically — you don't need to re-run it every time.
Part 9 — Complete the AIO setup wizard
- From any device, visit the AIO interface at
https://100.x.y.z:8080— use the Tailscale IP address here, not a domain, since this particular port (8080) uses AIO's own self-signed certificate, and HSTS can otherwise cause problems if you mix a domain and an IP on this port later. Your browser will warn you about the self-signed certificate; that's expected for the 8080 admin port specifically — accept/continue past it. - AIO shows you a one-time password (also stored at
/mnt/docker-aio-configinside the volume). Log in with it. - On the setup screen, enter your Tailscale MagicDNS name exactly as shown
by
tailscale status(e.g.,nextcloud-x270.tailxxxxx.ts.net). Submit it — since domain validation is skipped, this gets accepted immediately rather than trying to reach out and verify it. - Choose which optional containers to enable. Based on what you actually
want here:
- Enable Collabora Online. This is Nextcloud's self-hosted office suite — it gives you in-browser editing of documents, spreadsheets, and presentations, broadly equivalent to Word/Excel/PowerPoint, with everything staying on your own server. This is the piece that gets you "self-hosted office."
- Leave Talk disabled. Talk is Nextcloud's chat/video-calling feature, which is a separate thing from Collabora and isn't something you need for a single-user instance — skipping it saves RAM and CPU headroom that Collabora can use instead.
- Leave ClamAV, Full Text Search, and Whiteboard disabled too, unless you have a specific reason to want them. Each adds overhead for features you didn't ask for, and they're all one click away in the AIO interface later if you change your mind.
- Set the correct timezone at the bottom of this page before continuing — getting this wrong can cause database initialization issues that are annoying to fix after the fact.
- Click Download and start containers, and wait. This pulls several gigabytes of container images, so it'll take a while depending on your internet connection.
- Once everything shows green/running, you'll be given the actual Nextcloud admin login URL and an initial password. Save that password somewhere safe (a password manager, not a sticky note).
- From now on, access your Nextcloud at:
https://nextcloud-x270.tailxxxxx.ts.netfrom any device that's also connected to your tailnet. No port forwarding, no public exposure, real HTTPS, works from anywhere with internet access as long as both devices are on the tailnet.
Part 10 — Connect your devices
- Install Tailscale on every phone, tablet, and computer you want to use Nextcloud from (iOS App Store, Google Play, or the downloads page for desktop) and log into the same Tailscale account.
- Install the Nextcloud app (mobile) or Nextcloud Desktop Client (Windows/Mac/Linux) on those devices.
- When prompted for your server address, enter
https://nextcloud-x270.tailxxxxx.ts.net(your actual MagicDNS name). - Log in with your Nextcloud account. Since both devices are on the tailnet and Tailscale's certificate is fully valid, there shouldn't be any certificate warnings at this stage — that's specifically what the Tailscale Serve approach buys you over a raw self-signed setup.
- If you ever want to share access with family members without giving them your own Tailscale login, look into Tailscale's "Share" feature, which lets you invite specific devices to your tailnet without sharing your full account.
Part 11 — Backups (don't skip this)
Nextcloud AIO ships with a built-in backup system based on BorgBackup — encrypted, incremental, and deduplicated.
- In the AIO interface, go to the Backup section.
- Point it at a location that is not the internal NVMe your live data lives on — since you already have a NAS, the natural choice here is a network share (SMB/NFS) on that NAS, mounted on the X270. If a drive fails, you want your backup somewhere physically separate. If you'd rather use a plug-in external USB drive for backups instead, that works too — it just needs to not be the same drive as your live data.
- Set a backup schedule (daily is reasonable for a personal instance) and write down the encryption passphrase it generates — without it, the backup is useless to you even if you have the files. A password manager is the right place for this.
- Periodically (don't just set it and forget it forever) use AIO's restore-test feature or restore a backup to a scratch location to confirm it actually works. An untested backup is a hope, not a backup.
Given that this instance is mostly a transient landing zone before files move to your NAS anyway, backups here matter less for long-term archival and more for not losing the last day or two of phone photos/documents if the drive or the box itself dies before you've gotten around to moving them over.
For genuine disaster resilience (fire, theft, etc.), consider occasionally copying an encrypted backup off-site — to a cloud storage bucket, a drive at a family member's house, or similar. The Borg backups are already encrypted, so storing them somewhere you don't fully control is reasonably safe.
Part 12 — Keeping things updated
- Nextcloud AIO itself is self-updating in the sense that the AIO interface shows you when updates are available for the mastercontainer and the Nextcloud containers, and updating is a one-click "Update containers" button in the interface. It is not fully automatic by default — check in periodically (it'll show a notification badge when something's available).
- The host OS is handled by
unattended-upgrades, configured back in Part 3.4, for security patches. For full Ubuntu point releases (e.g. moving between 26.04.x point releases), runsudo apt update && sudo apt full-upgrade -yevery so often manually. - Docker Engine itself updates through the same apt repository you
added in Part 5 —
sudo apt upgradecovers it. - Tailscale updates via apt as well if you installed it through the
official script;
sudo apt update && sudo apt upgrade tailscalekeeps it current, or just letunattended-upgradeshandle it.
Security checklist (quick recap)
- No ports forwarded on your router for this at all — everything routes through Tailscale.
-
ufwenabled, only allowing SSH inbound on the LAN. - SSH key-based login, password auth disabled.
- Automatic OS security updates enabled.
- Apache/Nextcloud's port 11000 bound to
127.0.0.1only — never directly reachable, even on your LAN. - Tailscale ACLs reviewed if you ever add other people to your tailnet — by default everyone on a personal tailnet can reach everything, which is fine solo but worth tightening if you invite others.
- Backups configured, encrypted, stored separately from the live data, and occasionally test-restored.
- Strong, unique Nextcloud admin password, ideally with two-factor authentication enabled in Nextcloud's security settings.
Troubleshooting
AIO setup wizard says the domain can't be validated even with
SKIP_DOMAIN_VALIDATION=true set.
Double check the environment variable actually made it into the running
container: docker inspect nextcloud-aio-mastercontainer | grep -A5 SKIP_DOMAIN_VALIDATION. If you changed the compose file after the first
docker compose up -d, you need to recreate the container
(docker compose up -d --force-recreate) for env var changes to take
effect — editing the file alone doesn't update a running container.
First-run fails with a data directory permissions or "not empty"
error.
AIO wants /var/lib/nextcloud-data to exist and be empty, and it sets
ownership itself. If you pre-chowned it (an old habit from non-AIO
Nextcloud installs) or anything has been written into it, reset it:
sudo rm -rf /var/lib/nextcloud-data && sudo mkdir -p /var/lib/nextcloud-data, then recreate the containers.
Browser can't reach https://your-name.tailxxxxx.ts.net at all.
Confirm tailscale serve is actually active: tailscale serve status.
If it's empty, re-run the command from Part 8.3. Also confirm the device
you're browsing from is actually connected to the same tailnet
(tailscale status on that device).
Mobile/desktop client gives a certificate error.
This almost always means you accidentally connected to the IP-based
:8080 admin interface instead of the tailscale serve-bridged domain.
Use the .ts.net address, not the Tailscale IP, for the actual Nextcloud
client connections.
Everything feels slow.
Check docker stats to see which container is using the most CPU/RAM. If
Collabora or Talk is enabled and you're not actively using them,
disabling them in the AIO interface frees up meaningful resources on this
hardware.
Worried about filling up the drive since this is a "dropbox" that
should stay mostly empty.
A simple way to stay on top of it without manually checking is a tiny
cron job that emails or logs a warning when free space drops below a
threshold. Even simpler: just run df -h / occasionally, or check the
storage usage shown in Nextcloud's own admin settings page. Since the
workflow is "land here, then move to NAS," the fix when space gets tight
is usually just doing that transfer sooner rather than anything
technical.
After a power outage, is the server actually protected?
Thanks to the X270's dual battery acting as a UPS, brief outages
shouldn't even register. To confirm the battery is carrying the load
during an outage, run upower -i $(upower -e | grep BAT) or check
sensors/tpacpi-bat -g while on battery — you'll see it discharging.
Combined with the "Power On after Power Failure" BIOS setting from Part
1, the box rides out short outages on battery and cleanly reboots itself
after longer ones.
Useful command reference
# Check Tailscale status and your MagicDNS name
tailscale status
# Check what tailscale serve is currently bridging
tailscale serve status
# View AIO / Nextcloud container logs
docker compose -f ~/nextcloud-aio/compose.yaml logs -f
# See all running containers and resource usage
docker ps
docker stats
# Restart the whole AIO stack
cd ~/nextcloud-aio
docker compose restart
# Check disk usage on the data directory / root filesystem
df -h /var/lib/nextcloud-data
df -h /
# Check CPU temperature
sensors
# Check battery charge / discharge state (UPS sanity check)
upower -i $(upower -e | grep BAT)
tpacpi-bat -g ST 1 # external battery start threshold
tpacpi-bat -g ST 2 # internal battery start thresholdFurther reading
- Nextcloud AIO GitHub repo — the canonical source for every environment variable and edge case
- Nextcloud AIO reverse-proxy documentation — the official basis for the Tailscale Serve approach used in this guide
- Tailscale Serve documentation
- Tailscale Docs: Linux installation