---
title: Keeping the Website Running After a VPS Reboot
description: How to ensure your Podman-hosted website comes back automatically after a server restart.
pubDate: '2026-03-04T16:50:00+01:00'
category: en/on-premises-private-cloud
tags:
  - podman
  - systemd
seriesParent: astro-ssr-with-podman
seriesOrder: 7
---

The first time the VPS rebooted for a kernel update, the site stayed down until I SSH'd in and ran `podman-compose up -d` by hand. That's the kind of thing you only accept once. A deployment that survives its own runtime but not the box underneath it isn't really production.

This post is the minimal systemd wiring that makes reboot a non-event.

## The setup

- **VPS**: Debian, Podman running rootful, compose stack under `/opt/website`.
- **Reverse proxy**: Caddy (from the earlier post in this series) in front of the app container.
- **Goal**: after `sudo reboot`, everything — container, Caddy, TLS — is back on its own.

## Restart policy in Compose

Inside `compose.yml`:

```yaml
restart: unless-stopped
```

**Problem:** `restart: unless-stopped` is necessary but not sufficient. It tells Podman to restart the container if it crashes, but on a full reboot there's nobody running `podman-compose up` in the first place — nothing tells Podman about this compose stack at all.

**Implementation:** A systemd unit that owns the compose lifecycle end-to-end.

## A systemd service for the compose stack

`/etc/systemd/system/website-compose.service`:

```ini
[Unit]
Description=Website Podman Compose Stack
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/website
ExecStart=/usr/bin/podman-compose -f compose.yml up -d
ExecStop=/usr/bin/podman-compose -f compose.yml down
TimeoutStartSec=0

[Install]
WantedBy=multi-user.target
```

Two details worth calling out:

- `Type=oneshot` with `RemainAfterExit=yes` — `podman-compose up -d` returns immediately after starting the container, so systemd sees it exit; `RemainAfterExit` tells systemd to still consider the unit "active" afterwards.
- `After=network-online.target` — without this, the compose stack can race the network on cold boot and fail to pull or resolve anything.

Enable and start it:

```bash
sudo systemctl daemon-reload
sudo systemctl enable website-compose.service
sudo systemctl start website-compose.service
sudo systemctl status website-compose.service --no-pager
```

## Make sure Caddy also starts on boot

Caddy's Debian package enables its unit by default — but it's worth confirming rather than assuming:

```bash
sudo systemctl enable caddy
sudo systemctl status caddy --no-pager
```

## The reboot test

The only way to know the wiring is right is to actually reboot the machine:

```bash
sudo reboot
```

After reconnecting over SSH:

```bash
systemctl status website-compose.service --no-pager
podman ps
curl -I http://127.0.0.1:4321
curl -I https://adrian-altner.de
```

What I expect to see:

- `website-compose.service` active.
- The website container running.
- Both the local and public health checks return `200`.

## Troubleshooting after reboot

- **Container not running.** Logs first, always:
  `journalctl -u website-compose.service -n 100 --no-pager`
- **Caddy up but site down.** The app container is the usual suspect:
  `podman logs --tail=200 website`
- **TLS or proxy issues.** Caddy's journal tells you which hostname failed and why:
  `journalctl -u caddy -n 100 --no-pager`

## What to take away

- `restart: unless-stopped` covers crashes; systemd covers reboots — you need both.
- `After=network-online.target` prevents cold-boot races that look random and are hard to debug later.
- `Type=oneshot` + `RemainAfterExit=yes` is the right shape for a unit that wraps a tool which detaches after starting.
- Reboot the machine on purpose, once, before you ever need to trust the setup unattended.
