Configuring Your Server¶
Approximate Time: 30 minutes (this guide) + 30 minutes (Backing Up Your Server)
This guide assumes you have already created a new server according to Renting a Server or similar.
As noted in that guide, the server should have the latest version of Debian and be reachable via SSH.
ssh root@YOUR-IP-HERE
Securing the Server¶
The first thing to do is to secure the server. It's good to do this first so you don't forget to come back to it once your server is actually hosting a site.
Additionally, a mistake here comes with a risk of locking yourself out. Do this early so that if you need to delete the server and restart, you can easily do so without losing work.
Create a New User¶
apt install sudo
adduser admin
usermod -aG sudo admin
mkdir -p /home/admin/.ssh
cp ~/.ssh/authorized_keys /home/admin/.ssh/
chown -R admin:admin /home/admin/.ssh
chmod 700 /home/admin/.ssh
chmod 600 /home/admin/.ssh/authorized_keys
This creates a new user named admin (you can change this, but be sure to update all commands below).
This command will prompt for a few things, you can skip all of them except for the password. Be sure that you remember this password!
It also makes it possible to log in with the same SSH key you used to log in as root.
It is recommended that you avoid using the root account, instead using sudo as needed to elevate privileges.
Disable root SSH and password login¶
cat > /etc/ssh/sshd_config.d/00-hardening.conf <<'EOF'
PermitRootLogin no
PasswordAuthentication no
EOF
sshd -t && systemctl reload ssh
Bots are constantly trying to guess passwords for common account names on open SSH ports.
This adds a set of rules disabling dangerous logins: the root account and non-key based authentication.
Confirm Login Before Proceeding
Once root login is disabled, it will essential to confirm that you can login with the new account before proceeding.
Keep your current SSH window open, and open a new one.
Switch Users¶
Without closing the old SSH window. In a new window, run:
ssh admin@YOUR-IP-HERE
If this is unable to connect, carefully re-read prior steps and diagnose the error in your original SSH connection. Do not close the logged-in root session until this command succeeds.
Once you are logged in as admin in a second window, you can close the root SSH session window and proceed.
Enable fail2ban¶
The final command should give a positive confirmation that fail2ban is enabled for SSH.
fail2ban helps deter brute force attacks. With the SSH changes above, this is mostly to reduce log noise.
Enable unattended-upgrades¶
A prompt will appear, you should select (y)es.
This installs Debian security updates automatically, so known vulnerabilities get patched without you logging in.
Warning
This does not enable automatic reboots that are required for some updates. If you log in to the server and see a "reboot required" message you may want to manually reboot at a time that is convenient to you.
Configuring DNS¶
Before we proceed, we want to give this server a domain name. This will be required by the next step to enable HTTPS and make our site accesible in the browser.
This requires us to login to our domain registrar's website and access the DNS settings.
Porkbun's panel is here: https://porkbun.com/account/domainsSpeedy Under the domain you're using, click the button that says 'DNS'. This will open a side panel with a button that says '+ Add Record'.
Clicking this will reveal a form with a few fields:
- Type: CNAME
- Host: 'app' (or whatever subdomain you want to configure)
- IPv4 Address: your server's IP
- TTL: 600 (default)
This will have Porkbun publish a DNS record that will route requests to app.example.com to your IP.
Preparing to Run Applications¶
There are two common paths to handling long-running applications on the server.
One of these, systemd, is already running on the server and manages existing long-running processes.It is possible to run your own application as a systemd process, and install packages like caddy, postgres, redis, etc. using apt, which will give them systemd services as well.
We'll use docker instead, which comes with a bit of overhead, but allows the same environment to be used on your dev machine and other servers.
Installing Docker¶
sudo apt update
sudo apt install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/debian
Suites: $(. /etc/os-release && echo "$VERSION_CODENAME")
Components: stable
Signed-By: /etc/apt/keyrings/docker.asc
EOF
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
(These commands come from https://docs.docker.com/engine/install/debian/#install-using-the-repository .)
Confirm that this worked by running:
sudo docker run hello-world
If this finishes without error, your server is ready to run docker images.
Running the Application Stack¶
To run your own applications, you'll need to first Dockerize Your Application.
In the example below we'll use Miniflux, an RSS reader, as a stand-in application.
We are going to create three files, I suggest you place them in ~/compose/ but any directory is fine.
compose.yml¶
services:
caddy:
image: caddy:2
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
depends_on:
- miniflux
miniflux:
image: miniflux/miniflux:latest
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
DATABASE_URL: postgres://miniflux:${POSTGRES_PASSWORD}@db/miniflux?sslmode=disable
RUN_MIGRATIONS: 1
CREATE_ADMIN: 1
ADMIN_USERNAME: admin
ADMIN_PASSWORD: ${MINIFLUX_ADMIN_PASSWORD}
BASE_URL: https://app.example.com # CHANGE
db:
image: postgres:17
restart: unless-stopped
environment:
POSTGRES_USER: miniflux
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: miniflux
volumes:
- db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD", "pg_isready", "-U", "miniflux"]
interval: 10s
start_period: 30s
volumes:
caddy_data:
caddy_config:
db_data:
Caddyfile¶
.env¶
This configures three services:
- Caddy, our reverse proxy. This handles all incoming HTTP requests and dispatches them based on the rules in
Caddyfile. Our DNS settings from before ensure that requests forapp.example.comare sent to our server, and this file tells Caddy what to do, in this case to allow them to be processed by Miniflux.
This is what makes it possible to host many apps on a single server. Each app would need another DNS CNAME entry and another entry in the Caddyfile.
- Postgres, our database. Configured with a persistent data volume so that data survives the process being recreated.
- Miniflux, an RSS reader. Used here as a stand-in for a simple dynamic application.
Managing Secrets
We created an .env file, which is where secrets are kept.
Remember it is important that you never commit secrets to git, as doing so is likely to lead to accidental breaches.
If you've committed a credential to git already, removing it from git is not enough; the credential itself needs to be disabled on the server since it will survive in history and likely has already been harvested by malicious bots.
Starting Docker¶
If you run sudo docker compose up -d all of the services in compose.yml should start.
sudo docker compose ps should show all services as running after a few seconds.
sudo docker compose logs -f will allow you to see the current logs to monitor for any issues. It isn't uncommon for this to be noisy at startup, but if things quiet down with no errors printed, the application is running.
Congratulations, you've deployed an app!
You should now be able to visit app.example.com and see your application running in the browser!
Configuring Backups¶
One final step before we can say that we're done setting up the server: we want to configure backups.
Maintaining the Server¶
Having an application server is not as painless as static hosting. It takes some routine maintenance to keep it secure and running. An hour or less a month is plenty for a simple server.
Security Updates¶
We configured unattended-upgrades above, so this is mostly taken care of.
As mentioned, the server will not automatically reboot. Keep an eye on messages to reboot the server and do this manually as needed.
Docker Image Updates¶
Occasionally, you may want to update the services running in docker.
To do this, run:
Once again, you can use sudo docker compose ps and/or docker compose logs -f to see if everything is working as intended.
This pulls the latest images, restarts the services that changed, and prunes unused images.
Warning
Updating pinned docker image versions (like postgres:17 to postgres:18) is out of scope for this guide and should only be done with great care.
You may want to try any version updates locally to ensure the new versions work as expected.
Next Steps¶
The final part of this process is Backing Up Your Server.