From abe765a6903834329985f6d69bdcc6a437c97f21 Mon Sep 17 00:00:00 2001 From: kaiwester Date: Wed, 1 Jul 2026 17:11:57 +0200 Subject: [PATCH] /home/kaiwester/projects/github/kaiwester/community-tutorials/community-tutorials/private-vlan-postgres-pgadmin-two-vservers --- .../01-en.md | 382 +++++++++++++++++ .../02-de.md | 384 ++++++++++++++++++ 2 files changed, 766 insertions(+) create mode 100644 community-tutorials/private-vlan-postgres-pgadmin-two-vservers/01-en.md create mode 100644 community-tutorials/private-vlan-postgres-pgadmin-two-vservers/02-de.md diff --git a/community-tutorials/private-vlan-postgres-pgadmin-two-vservers/01-en.md b/community-tutorials/private-vlan-postgres-pgadmin-two-vservers/01-en.md new file mode 100644 index 00000000..c0c993a0 --- /dev/null +++ b/community-tutorials/private-vlan-postgres-pgadmin-two-vservers/01-en.md @@ -0,0 +1,382 @@ +--- +title: Isolate a PostgreSQL Database Behind a Cloud vLAN on netcup +description: Learn how to connect two netcup vServers over a Cloud vLAN so PostgreSQL runs on a database server with no public IP, reachable only through a public edge server running pgAdmin. +level: [intermediate] +updated_at: 2026-07-01 +slug: private-vlan-postgres-pgadmin-two-vservers +author_name: Kai Wester +author_url: https://github.com/kaiwester +author_image: +author_bio: +tags: [debian, postgresql, pgadmin, vlan, cloud-vlan, networking, nat, security, netcup] +netcup_product_url: https://www.netcup.com/en/server/vps +language: en +available_languages: [en, de] +--- + +# Introduction + +Running a database directly on the public internet is one of the most common ways servers get compromised. Every open PostgreSQL port is a target for brute-force login attempts and exploits. The safest database server is one that has no public IP address at all. + +In this tutorial we build exactly that. We use two netcup vServers connected by a **Cloud vLAN** (virtual Local Area Network - a private network that only your own servers can see): + +- An **edge server** with a public IP address. It is small (1 core, 1 GB RAM), runs **pgAdmin** (a web-based administration tool for PostgreSQL), and acts as the single, hardened entry point to your setup. It also works as a **NAT gateway** (Network Address Translation - it lets the private server reach the internet through the edge server's public IP) so the database server can still download package updates. +- A **database server** with more resources (4 cores, 8 GB RAM) that runs **PostgreSQL** and has **no public IP address**. It is reachable only over the private Cloud vLAN. + +The resulting architecture looks like this: + +``` + Internet + | + public IPv4/IPv6 (ufw: SSH + HTTPS) + | + +-----------------------+ Cloud vLAN 10.132.0.0/24 + | EDGE (1 core/1 GB) | eth1: 10.132.0.10 -----+ + | - pgAdmin4 (web) | | + | - NAT gateway | | + +-----------------------+ | + | + +-----------------------+ | + | DB (4 core/8 GB) | eth1: 10.132.0.20 -----+ + | - PostgreSQL | (NO public IP) + | - default route -> 10.132.0.10 (NAT) + +-----------------------+ +``` + +The reading time of this tutorial is about 15 minutes; implementation will take approximately 90 minutes. + +All examples were tested on **Debian 13 (Trixie)** in July 2026 and can be adapted to other distributions with minor changes. Throughout the tutorial the two servers are referred to as **s-edge** (the public edge server) and **s-db** (the private database server). When a command must be run on both, it is stated explicitly; otherwise it belongs to the named server only. + +The tutorial uses the example private IP addresses `10.132.0.10` (s-edge) and `10.132.0.20` (s-db), and the placeholder `` for the edge server's public IP address. Replace these with your own values as you follow along. + +# Requirements + +- Two netcup vServers, both running Debian 13: + - **s-edge** - the edge server, 1 core / 1 GB RAM is sufficient, e.g. [VPS pico G11s](https://www.netcup.com/de/server/vps/vps-pico-g11s-12m-nue). + - **s-db** - the database server, 4 cores / 8 GB RAM as requested for a PostgreSQL workload, e.g. [VPS Lite 2 G12s](https://www.netcup.com/de/server/vps/vps-lite-2-g12s-3m). +- A [Cloud vLAN Free](https://www.netcup.com/de/erweiterungen-kvm/cloud-vlan-free-nue) (or higher) added to both servers. +- Access to your [Customer Control Panel (CCP)](https://www.customercontrolpanel.de) and [Server Control Panel (SCP)](https://www.servercontrolpanel.de). +- A user with root privileges (`sudo`) on both servers. + +This tutorial assumes you have already ordered the products above and completed basic server hardening. If you have not yet secured your servers with SSH key authentication and a non-root user, follow the [First steps to secure your Linux server](https://community.netcup.com/en/tutorials/first-steps-to-protect-your-linux-server-against-common-attacks) tutorial **first**, on both servers. We build the firewall on top of that baseline here. + +> **Info** +> A Cloud vLAN Free is a layer-2 private network. Any server you attach to the same vLAN can talk to any other on it, isolated from the public internet and from other netcup customers. + +# Step 1 - Attach the Cloud vLAN in the SCP + +First, give each server a second network interface that is connected to your Cloud vLAN. This is done entirely in the web-based Server Control Panel (SCP). + +1. Log in to your [SCP](https://www.servercontrolpanel.de), either directly or via the autologin feature in your [CCP](https://www.customercontrolpanel.de). +2. Choose **s-edge** from the server list on the top left. +3. Navigate to the **Network** section in the left sidebar. +4. Choose **add Ethernet Interface** on the top right. +5. Select your Cloud vLAN and choose **virtio** as the driver. +6. Repeat steps 2–5 for **s-db**, then continue with Step 2. + +After a reboot (or a network restart, covered below) each server has a new interface for the vLAN. On netcup Debian images this is usually `eth1`, but it may appear as `ens7` or similar. Verify the exact name with `ip addr` before continuing. + +# Step 2 - Configure the vLAN interface + +The new interface has no IP address yet. We assign static private addresses using the classic `ifupdown` network stack, which is used by netcup's Debian images. + +On a minimal installation, `ifupdown` may not be present. Install it on **both servers** if needed: + +```bash +sudo apt update +sudo apt install ifupdown +``` + +## Step 2.1 - Configure s-edge + +On **s-edge**, create the vLAN configuration with your favorite text editor, e.g. `sudo nano /etc/network/interfaces.d/vlan.cfg`: + +``` +auto eth1 +iface eth1 inet static + address 10.132.0.10 + netmask 255.255.255.0 +``` + +## Step 2.2 - Configure s-db + +On **s-db**, create the same file but with the second address. Because s-db will have no public IP, it also needs the edge server as its **default gateway** (the route through which all internet-bound traffic leaves) and it needs DNS resolvers (which translate hostnames like `deb.debian.org` into IP addresses): + +``` +auto eth1 +iface eth1 inet static + address 10.132.0.20 + netmask 255.255.255.0 + gateway 10.132.0.10 + dns-nameservers 46.38.225.230 46.38.252.230 +``` + +> **Info** +> `46.38.225.230` and `46.38.252.230` are netcup's public IPv4 DNS resolvers. + +## Step 2.3 - Restart networking + +Apply the configuration on **both servers**: + +```bash +sudo systemctl restart networking +``` + +# Step 3 - Verify vLAN connectivity + +Before going further, confirm the private network actually works. + +On **s-edge**, check that the interface has its address: + +```bash +ip addr show eth1 +``` + +You should see `inet 10.132.0.10/24` on the interface. Now ping s-db over the vLAN: + +```bash +ping 10.132.0.20 -c 5 +``` + +``` +PING 10.132.0.20 (10.132.0.20) 56(84) bytes of data. +64 bytes from 10.132.0.20: icmp_seq=1 ttl=64 time=0.450 ms +64 bytes from 10.132.0.20: icmp_seq=2 ttl=64 time=0.510 ms +... +--- 10.132.0.20 ping statistics --- +5 packets transmitted, 5 received, 0% packet loss, time 104ms +``` + +Then run the reverse ping from **s-db** to `10.132.0.10` to confirm the connection works both ways. Once both directions succeed, the vLAN is ready. + +# Step 4 - Make the edge server a NAT gateway + +The database server has no public IP, so on its own it cannot reach the internet for package updates. We solve this by turning **s-edge** into a NAT gateway: it forwards s-db's outbound traffic and rewrites it to use its own public IP. + +All commands in this step run on **s-edge**. + +## Step 4.1 - Enable IP forwarding + +By default Linux does not forward packets between interfaces. Enable it. Edit `/etc/sysctl.conf`, e.g. `sudo nano /etc/sysctl.conf`, and make sure this line is present and uncommented: + +``` +net.ipv4.ip_forward = 1 +``` + +Apply it immediately: + +```bash +sudo sysctl -p +``` + +## Step 4.2 - Configure NAT in ufw + +We manage the firewall and NAT with **ufw** (Uncomplicated Firewall). First allow forwarded traffic. Edit `/etc/default/ufw`, e.g. `sudo nano /etc/default/ufw`, and change the forward policy: + +``` +DEFAULT_FORWARD_POLICY="ACCEPT" +``` + +Next add the NAT masquerade rule. Edit `/etc/ufw/before.rules`, e.g. `sudo nano /etc/ufw/before.rules`, and add the following block **at the very top of the file**, before the existing `*filter` line. Replace `eth0` with your public interface and `eth1` with your vLAN interface if they differ: + +``` +*nat +:POSTROUTING ACCEPT [0:0] +# Masquerade traffic from the Cloud vLAN going out via the public interface +-A POSTROUTING -s 10.132.0.0/24 -o eth0 -j MASQUERADE +COMMIT +``` + +Reload ufw to apply (if ufw is not enabled yet, that happens in Step 7 - the rule takes effect once enabled): + +```bash +sudo ufw reload +``` + +## Step 4.3 - Verify internet access from s-db + +On **s-db**, confirm it can now reach the internet through the gateway: + +```bash +ping 1.1.1.1 -c 3 +sudo apt update +``` + +Both should succeed even though s-db still has no meaningful public presence. If `apt update` hangs on DNS, re-check the `dns-nameservers` line from Step 2.2. + +# Step 5 - Install and configure PostgreSQL + +All commands in this step run on **s-db**. + +## Step 5.1 - Install PostgreSQL + +```bash +sudo apt update +sudo apt install postgresql +``` + +## Step 5.2 - Bind PostgreSQL to the vLAN only + +By default PostgreSQL listens only on `localhost`. We want it to listen on the **private vLAN address only** - never on `*` (all interfaces) and never on a public IP. This single setting is what keeps the database off any public network. + +Find your config file location: + +```bash +sudo -u postgres psql -c 'SHOW config_file;' +``` + +On Debian 13 this is typically `/etc/postgresql/17/main/postgresql.conf`. Edit it and set: + +``` +listen_addresses = '10.132.0.20' +``` + +## Step 5.3 - Allow the edge server to connect + +Edit `pg_hba.conf` in the same directory (e.g. `/etc/postgresql/17/main/pg_hba.conf`). This file (`pg_hba` stands for "host-based authentication") controls which clients may connect. Add a line permitting **only** the edge server's vLAN address, using encrypted password authentication: + +``` +# TYPE DATABASE USER ADDRESS METHOD +host all all 10.132.0.10/32 scram-sha-256 +``` + +## Step 5.4 - Create a database and role + +Create an application role (user) and a database for it. Replace the example password with a strong one: + +```bash +sudo -u postgres psql +``` + +```sql +CREATE ROLE appuser WITH LOGIN PASSWORD 'changeMe.aHighlyComplexPassword'; +CREATE DATABASE appdb OWNER appuser; +\q +``` + +## Step 5.5 - Restart PostgreSQL + +```bash +sudo systemctl restart postgresql +``` + +Confirm PostgreSQL is now listening on the vLAN address: + +```bash +sudo ss -tlnp | grep 5432 +``` + +You should see it bound to `10.132.0.20:5432` and nothing else. + +# Step 6 - Install pgAdmin in server mode + +pgAdmin gives you a web interface to manage PostgreSQL. We install it natively in **server mode** (running behind the Apache web server), which is lighter on the 1 GB edge server than a container. + +All commands in this step run on **s-edge**. + +## Step 6.1 - Add the pgAdmin repository and install + +```bash +# Import the repository signing key +curl -fsS https://www.pgadmin.org/static/packages_pgadmin_org.pub | sudo gpg --dearmor -o /usr/share/keyrings/packages-pgadmin-org.gpg + +# Add the repository (uses the Debian codename automatically) +sudo sh -c 'echo "deb [signed-by=/usr/share/keyrings/packages-pgadmin-org.gpg] https://ftp.postgresql.org/pub/pgadmin/pgadmin4/apt/$(lsb_release -cs) pgadmin4 main" > /etc/apt/sources.list.d/pgadmin4.list' + +sudo apt update +sudo apt install pgadmin4-web +``` + +> **Info** +> If no pgAdmin package exists yet for the Debian 13 codename `trixie`, replace `$(lsb_release -cs)` with the most recent supported codename (for example `bookworm`) in the command above. + +## Step 6.2 - Configure the web interface + +Run the setup script. It configures Apache and asks you to create a pgAdmin login (email and password): + +```bash +sudo /usr/pgadmin4/bin/setup-web.sh +``` + +You can now open pgAdmin in your browser at: + +``` +https:///pgadmin4 +``` + +## Step 6.3 - Connect to PostgreSQL over the vLAN + +Log in to pgAdmin, then add the database server as a new connection: + +- **Host name/address:** `10.132.0.20` (the private vLAN address of s-db) +- **Port:** `5432` +- **Username:** `appuser` +- **Password:** the password from Step 5.4 + +For a detailed walkthrough of the pgAdmin connection form, see the [Installing PostgreSQL and pgAdmin with Docker](https://community.netcup.com/en/tutorials/how-to-install-postgres-and-pgadmin-with-docker-and-connect-pgadmin-with-postgres) tutorial. The connection travels entirely over the private vLAN - it never touches the public internet. + +# Step 7 - Firewall both servers with ufw + +Now lock both servers down. We assume `ufw` is installed (it is part of the baseline hardening tutorial linked in the requirements); if not, `sudo apt install ufw` on both. + +## Step 7.1 - Firewall the edge server + +On **s-edge**, deny all incoming traffic by default, then allow only SSH and the web ports (80 for the Let's Encrypt HTTP challenge, 443 for pgAdmin over HTTPS): + +```bash +sudo ufw default deny incoming +sudo ufw default allow outgoing +sudo ufw allow OpenSSH +sudo ufw allow 80/tcp +sudo ufw allow 443/tcp +sudo ufw enable +``` + +## Step 7.2 - Firewall the database server + +On **s-db**, deny everything incoming, then allow **only the edge server** to reach PostgreSQL and SSH over the vLAN: + +```bash +sudo ufw default deny incoming +sudo ufw default allow outgoing +sudo ufw allow from 10.132.0.10 to any port 22 proto tcp +sudo ufw allow from 10.132.0.10 to any port 5432 proto tcp +sudo ufw enable +``` + +> **Tip** +> Because SSH to s-db is now allowed only from the edge server, you reach s-db through the edge as a jump host: `ssh -J operator@ operator@10.132.0.20`. + +# Conclusion + +You now have a PostgreSQL database server with **no public IP address**, reachable only across a private Cloud vLAN. All administration flows through a single, hardened edge server: pgAdmin for the database, and an SSH jump host for the shell. The edge server also provides the database with internet access for updates via NAT. The attack surface of the database has been reduced from "the entire internet" to "one server you control." + +Some suggested next steps: + +- Enable TLS between pgAdmin and PostgreSQL for encryption on the vLAN as well. +- Install [Fail2Ban](https://community.netcup.com/en/tutorials/how-to-install-and-configure-fail2ban-on-a-debian-server) on the edge server to block brute-force SSH attempts. +- Set up automated PostgreSQL backups on the database server. + +# Licence + +[MIT](https://github.com/netcup-community/community-tutorials/blob/main/LICENSE) + +Copyright (c) 2026 netcup + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicence, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# Contributor's Certificate of Origin + +By making a contribution to this project, I certify that: + +1. The contribution was created in whole or in part by me and I have the right to submit it under the licence indicated in the file; or + +2. The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate licence and I have the right under that licence to submit that work with modifications, whether created in whole or in part by me, under the same licence (unless I am permitted to submit under a different licence), as indicated in the file; or + +3. The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. + +4. I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the licence(s) involved. diff --git a/community-tutorials/private-vlan-postgres-pgadmin-two-vservers/02-de.md b/community-tutorials/private-vlan-postgres-pgadmin-two-vservers/02-de.md new file mode 100644 index 00000000..536c374d --- /dev/null +++ b/community-tutorials/private-vlan-postgres-pgadmin-two-vservers/02-de.md @@ -0,0 +1,384 @@ +--- +title: PostgreSQL-Datenbank hinter einem Cloud vLAN auf netcup isolieren +description: Lerne, wie du zwei netcup vServer über ein Cloud vLAN verbindest, damit PostgreSQL auf einem Datenbankserver ohne öffentliche IP läuft, der nur über einen öffentlichen Edge-Server mit pgAdmin erreichbar ist. +level: [intermediate] +updated_at: 2026-07-01 +slug: private-vlan-postgres-pgadmin-two-vservers +author_name: Kai Wester +translator_url: https://github.com/kaiwester +translator_name: Kai Wester +author_url: https://github.com/kaiwester +author_image: +author_bio: +tags: [debian, postgresql, pgadmin, vlan, cloud-vlan, networking, nat, security, netcup] +netcup_product_url: https://www.netcup.com/en/server/vps +language: de +available_languages: [en, de] +--- + +# Einführung + +Eine Datenbank direkt im öffentlichen Internet zu betreiben ist einer der häufigsten Wege, wie Server kompromittiert werden. Jeder offene PostgreSQL-Port ist ein Ziel für Brute-Force-Login-Versuche und Exploits. Der sicherste Datenbankserver ist einer, der überhaupt keine öffentliche IP-Adresse hat. + +In diesem Tutorial bauen wir genau das. Wir verwenden zwei netcup vServer, die über ein **Cloud vLAN** verbunden sind (ein virtuelles lokales Netzwerk - ein privates Netzwerk, das nur deine eigenen Server sehen können): + +- Ein **Edge-Server** mit öffentlicher IP-Adresse. Er ist klein (1 Kern, 1 GB RAM), betreibt **pgAdmin** (ein webbasiertes Administrationstool für PostgreSQL) und fungiert als einziger, gehärteter Einstiegspunkt in dein Setup. Zusätzlich arbeitet er als **NAT-Gateway** (Network Address Translation - er lässt den privaten Server über die öffentliche IP des Edge-Servers auf das Internet zugreifen), damit der Datenbankserver weiterhin Paket-Updates herunterladen kann. +- Ein **Datenbankserver** mit mehr Ressourcen (4 Kerne, 8 GB RAM), der **PostgreSQL** betreibt und **keine öffentliche IP-Adresse** hat. Er ist nur über das private Cloud vLAN erreichbar. + +Die resultierende Architektur sieht so aus: + +``` + Internet + | + public IPv4/IPv6 (ufw: SSH + HTTPS) + | + +-----------------------+ Cloud vLAN 10.132.0.0/24 + | EDGE (1 core/1 GB) | eth1: 10.132.0.10 -----+ + | - pgAdmin4 (web) | | + | - NAT gateway | | + +-----------------------+ | + | + +-----------------------+ | + | DB (4 core/8 GB) | eth1: 10.132.0.20 -----+ + | - PostgreSQL | (NO public IP) + | - default route -> 10.132.0.10 (NAT) + +-----------------------+ +``` + +Die Lesezeit dieses Tutorials beträgt etwa 15 Minuten; die Umsetzung dauert ungefähr 90 Minuten. + +Alle Beispiele wurden im Juli 2026 auf **Debian 13 (Trixie)** getestet und lassen sich mit kleinen Anpassungen auf andere Distributionen übertragen. Im gesamten Tutorial werden die beiden Server als **s-edge** (der öffentliche Edge-Server) und **s-db** (der private Datenbankserver) bezeichnet. Muss ein Befehl auf beiden Servern ausgeführt werden, wird das explizit angegeben; andernfalls gehört er nur zum genannten Server. + +Das Tutorial verwendet die beispielhaften privaten IP-Adressen `10.132.0.10` (s-edge) und `10.132.0.20` (s-db) sowie den Platzhalter `` für die öffentliche IP-Adresse des Edge-Servers. Ersetze diese beim Durcharbeiten durch deine eigenen Werte. + +# Anforderungen + +- Zwei netcup vServer, beide mit Debian 13: + - **s-edge** - der Edge-Server, 1 Kern / 1 GB RAM sind ausreichend, z. B. [VPS pico G11s](https://www.netcup.com/de/server/vps/vps-pico-g11s-12m-nue). + - **s-db** - der Datenbankserver, 4 Kerne / 8 GB RAM wie für eine PostgreSQL-Workload empfohlen, z. B. [VPS Lite 2 G12s](https://www.netcup.com/de/server/vps/vps-lite-2-g12s-3m). +- Ein [Cloud vLAN Free](https://www.netcup.com/de/erweiterungen-kvm/cloud-vlan-free-nue) (oder höher), das beiden Servern hinzugefügt wurde. +- Zugang zu deinem [Customer Control Panel (CCP)](https://www.customercontrolpanel.de) und [Server Control Panel (SCP)](https://www.servercontrolpanel.de). +- Ein Benutzer mit Root-Rechten (`sudo`) auf beiden Servern. + +Dieses Tutorial setzt voraus, dass du die oben genannten Produkte bereits bestellt und die grundlegende Server-Härtung bereits durchgeführt hast. Falls du deine Server noch nicht mit SSH-Key-Authentifizierung und einem Nicht-Root-Benutzer abgesichert hast, folge zuerst auf **beiden** Servern dem Tutorial [Erste Schritte zum Schutz deines Linux-Servers](https://community.netcup.com/de/tutorials/first-steps-to-protect-your-linux-server-against-common-attacks). Wir bauen die Firewall hier auf dieser Basis auf. + +> **Info** +> Ein Cloud vLAN Free ist ein privates Layer-2-Netzwerk. Jeder Server, den du an dasselbe vLAN anschließt, kann mit jedem anderen darin kommunizieren, isoliert vom öffentlichen Internet und von anderen netcup-Kunden. + +# Schritt 1 - Das Cloud vLAN im SCP anschließen + +Gib zunächst jedem Server eine zweite Netzwerkschnittstelle, die mit deinem Cloud vLAN verbunden ist. Dies erfolgt vollständig im webbasierten Server Control Panel (SCP). + +1. Melde dich in deinem [SCP](https://www.servercontrolpanel.de) an, entweder direkt oder über die Autologin-Funktion in deinem [CCP](https://www.customercontrolpanel.de). +2. Wähle **s-edge** aus der Serverliste oben links. +3. Navigiere im linken Seitenmenü zum Bereich **Netzwerk**. +4. Wähle oben rechts **Ethernet-Schnittstelle hinzufügen**. +5. Wähle dein Cloud vLAN aus und als Treiber **virtio**. +6. Wiederhole Schritte 2–5 für **s-db** und fahre dann mit Schritt 2 fort. + +Nach einem Neustart (oder einem Netzwerk-Restart, siehe unten) hat jeder Server eine neue Schnittstelle für das vLAN. Bei netcup-Debian-Images ist das üblicherweise `eth1`, kann aber auch als `ens7` oder ähnlich erscheinen. Prüfe den genauen Namen mit `ip addr`, bevor du fortfährst. + +# Schritt 2 - Die vLAN-Schnittstelle konfigurieren + +Die neue Schnittstelle hat noch keine IP-Adresse. Wir weisen statische private Adressen über den klassischen `ifupdown`-Netzwerkstack zu, der von netcups Debian-Images verwendet wird. + +Bei einer minimalen Installation ist `ifupdown` möglicherweise nicht vorhanden. Installiere es bei Bedarf auf **beiden Servern**: + +```bash +sudo apt update +sudo apt install ifupdown +``` + +## Schritt 2.1 - s-edge konfigurieren + +Erstelle auf **s-edge** die vLAN-Konfiguration mit deinem bevorzugten Texteditor, z. B. `sudo nano /etc/network/interfaces.d/vlan.cfg`: + +``` +auto eth1 +iface eth1 inet static + address 10.132.0.10 + netmask 255.255.255.0 +``` + +## Schritt 2.2 - s-db konfigurieren + +Erstelle auf **s-db** dieselbe Datei, jedoch mit der zweiten Adresse. Da s-db keine öffentliche IP hat, braucht er zusätzlich den Edge-Server als **Standard-Gateway** (die Route, über die aller internetgebundene Verkehr das System verlässt) sowie DNS-Resolver (die Hostnamen wie `deb.debian.org` in IP-Adressen übersetzen): + +``` +auto eth1 +iface eth1 inet static + address 10.132.0.20 + netmask 255.255.255.0 + gateway 10.132.0.10 + dns-nameservers 46.38.225.230 46.38.252.230 +``` + +> **Info** +> `46.38.225.230` und `46.38.252.230` sind die öffentlichen IPv4-DNS-Resolver von netcup. + +## Schritt 2.3 - Netzwerk neu starten + +Wende die Konfiguration auf **beiden Servern** an: + +```bash +sudo systemctl restart networking +``` + +# Schritt 3 - vLAN-Konnektivität prüfen + +Bevor du weitermachst, bestätige, dass das private Netzwerk tatsächlich funktioniert. + +Prüfe auf **s-edge**, ob die Schnittstelle ihre Adresse hat: + +```bash +ip addr show eth1 +``` + +Du solltest `inet 10.132.0.10/24` auf der Schnittstelle sehen. Pinge nun s-db über das vLAN an: + +```bash +ping 10.132.0.20 -c 5 +``` + +``` +PING 10.132.0.20 (10.132.0.20) 56(84) bytes of data. +64 bytes from 10.132.0.20: icmp_seq=1 ttl=64 time=0.450 ms +64 bytes from 10.132.0.20: icmp_seq=2 ttl=64 time=0.510 ms +... +--- 10.132.0.20 ping statistics --- +5 packets transmitted, 5 received, 0% packet loss, time 104ms +``` + +Führe anschließend den umgekehrten Ping von **s-db** zu `10.132.0.10` aus, um zu bestätigen, dass die Verbindung in beide Richtungen funktioniert. Sobald beide Richtungen erfolgreich sind, ist das vLAN bereit. + +# Schritt 4 - Den Edge-Server zum NAT-Gateway machen + +Der Datenbankserver hat keine öffentliche IP und kann daher von sich aus nicht für Paket-Updates auf das Internet zugreifen. Wir lösen das, indem wir **s-edge** zu einem NAT-Gateway machen: Er leitet den ausgehenden Verkehr von s-db weiter und schreibt ihn so um, dass er seine eigene öffentliche IP verwendet. + +Alle Befehle in diesem Schritt laufen auf **s-edge**. + +## Schritt 4.1 - IP-Forwarding aktivieren + +Standardmäßig leitet Linux keine Pakete zwischen Schnittstellen weiter. Aktiviere dies. Bearbeite `/etc/sysctl.conf`, z. B. mit `sudo nano /etc/sysctl.conf`, und stelle sicher, dass diese Zeile vorhanden und nicht auskommentiert ist: + +``` +net.ipv4.ip_forward = 1 +``` + +Wende die Änderung sofort an: + +```bash +sudo sysctl -p +``` + +## Schritt 4.2 - NAT in ufw konfigurieren + +Wir verwalten Firewall und NAT mit **ufw** (Uncomplicated Firewall). Erlaube zunächst weitergeleiteten Verkehr. Bearbeite `/etc/default/ufw`, z. B. mit `sudo nano /etc/default/ufw`, und ändere die Forward-Policy: + +``` +DEFAULT_FORWARD_POLICY="ACCEPT" +``` + +Füge als Nächstes die NAT-Masquerade-Regel hinzu. Bearbeite `/etc/ufw/before.rules`, z. B. mit `sudo nano /etc/ufw/before.rules`, und fügen den folgenden Block **ganz am Anfang der Datei** ein, vor der bestehenden `*filter`-Zeile. Ersetze `eth0` durch deine öffentliche Schnittstelle und `eth1` durch deine vLAN-Schnittstelle, falls diese abweichen: + +``` +*nat +:POSTROUTING ACCEPT [0:0] +# Masquerade traffic from the Cloud vLAN going out via the public interface +-A POSTROUTING -s 10.132.0.0/24 -o eth0 -j MASQUERADE +COMMIT +``` + +Lade ufw neu, um die Änderung anzuwenden (falls ufw noch nicht aktiviert ist, geschieht dies in Schritt 7 - die Regel greift, sobald es aktiviert ist): + +```bash +sudo ufw reload +``` + +## Schritt 4.3 - Internetzugang von s-db prüfen + +Bestätige auf **s-db**, dass es jetzt über das Gateway auf das Internet zugreifen kann: + +```bash +ping 1.1.1.1 -c 3 +sudo apt update +``` + +Beide sollten erfolgreich sein, obwohl s-db weiterhin keine nennenswerte öffentliche Präsenz hat. Wenn `apt update` bei der DNS-Auflösung hängen bleibt, überprüfe erneut die Zeile `dns-nameservers` aus Schritt 2.2. + +# Schritt 5 - PostgreSQL installieren und konfigurieren + +Alle Befehle in diesem Schritt laufen auf **s-db**. + +## Schritt 5.1 - PostgreSQL installieren + +```bash +sudo apt update +sudo apt install postgresql +``` + +## Schritt 5.2 - PostgreSQL nur an das vLAN binden + +Standardmäßig lauscht PostgreSQL nur auf `localhost`. Wir wollen, dass es **nur auf der privaten vLAN-Adresse** lauscht - niemals auf `*` (alle Schnittstellen) und niemals auf einer öffentlichen IP. Diese eine Einstellung ist es, die die Datenbank von jedem öffentlichen Netzwerk fernhält. + +Finde den Speicherort deiner Konfigurationsdatei: + +```bash +sudo -u postgres psql -c 'SHOW config_file;' +``` + +Auf Debian 13 ist das typischerweise `/etc/postgresql/17/main/postgresql.conf`. Bearbeite sie und setze: + +``` +listen_addresses = '10.132.0.20' +``` + +## Schritt 5.3 - Dem Edge-Server die Verbindung erlauben + +Bearbeite `pg_hba.conf` im selben Verzeichnis (z. B. `/etc/postgresql/17/main/pg_hba.conf`). Diese Datei (`pg_hba` steht für "host-based authentication") steuert, welche Clients sich verbinden dürfen. Füge eine Zeile hinzu, die **ausschließlich** die vLAN-Adresse des Edge-Servers zulässt, mit verschlüsselter Passwort-Authentifizierung: + +``` +# TYPE DATABASE USER ADDRESS METHOD +host all all 10.132.0.10/32 scram-sha-256 +``` + +## Schritt 5.4 - Datenbank und Rolle erstellen + +Erstelle eine Anwendungsrolle (Benutzer) und eine Datenbank dafür. Ersetze das Beispielpasswort durch ein starkes Passwort: + +```bash +sudo -u postgres psql +``` + +```sql +CREATE ROLE appuser WITH LOGIN PASSWORD 'changeMe.aHighlyComplexPassword'; +CREATE DATABASE appdb OWNER appuser; +\q +``` + +## Schritt 5.5 - PostgreSQL neu starten + +```bash +sudo systemctl restart postgresql +``` + +Bestätige, dass PostgreSQL jetzt auf der vLAN-Adresse lauscht: + +```bash +sudo ss -tlnp | grep 5432 +``` + +Du solltest sehen, dass es an `10.132.0.20:5432` gebunden ist und an nichts sonst. + +# Schritt 6 - pgAdmin im Server-Modus installieren + +pgAdmin gibt dir eine Weboberfläche zur Verwaltung von PostgreSQL. Wir installieren es nativ im **Server-Modus** (läuft hinter dem Apache-Webserver), was für den 1-GB-Edge-Server leichter ist als ein Container. + +Alle Befehle in diesem Schritt laufen auf **s-edge**. + +## Schritt 6.1 - Das pgAdmin-Repository hinzufügen und installieren + +```bash +# Import the repository signing key +curl -fsS https://www.pgadmin.org/static/packages_pgadmin_org.pub | sudo gpg --dearmor -o /usr/share/keyrings/packages-pgadmin-org.gpg + +# Add the repository (uses the Debian codename automatically) +sudo sh -c 'echo "deb [signed-by=/usr/share/keyrings/packages-pgadmin-org.gpg] https://ftp.postgresql.org/pub/pgadmin/pgadmin4/apt/$(lsb_release -cs) pgadmin4 main" > /etc/apt/sources.list.d/pgadmin4.list' + +sudo apt update +sudo apt install pgadmin4-web +``` + +> **Info** +> Falls es für den Debian-13-Codenamen `trixie` noch kein pgAdmin-Paket gibt, ersetze `$(lsb_release -cs)` im obigen Befehl durch den zuletzt unterstützten Codenamen (zum Beispiel `bookworm`). + +## Schritt 6.2 - Die Weboberfläche konfigurieren + +Führe das Setup-Skript aus. Es konfiguriert Apache und fordert dich auf, einen pgAdmin-Login (E-Mail und Passwort) anzulegen: + +```bash +sudo /usr/pgadmin4/bin/setup-web.sh +``` + +Du kannst pgAdmin nun in deinem Browser unter folgender Adresse öffnen: + +``` +https:///pgadmin4 +``` + +## Schritt 6.3 - Über das vLAN mit PostgreSQL verbinden + +Melde dich bei pgAdmin an und füge dann den Datenbankserver als neue Verbindung hinzu: + +- **Host name/address:** `10.132.0.20` (die private vLAN-Adresse von s-db) +- **Port:** `5432` +- **Username:** `appuser` +- **Password:** das Passwort aus Schritt 5.4 + +Eine ausführliche Anleitung zum pgAdmin-Verbindungsformular findest du im Tutorial [Installing PostgreSQL and pgAdmin with Docker](https://community.netcup.com/de/tutorials/how-to-install-postgres-and-pgadmin-with-docker-and-connect-pgadmin-with-postgres). Die Verbindung läuft vollständig über das private vLAN - sie berührt niemals das öffentliche Internet. + +# Schritt 7 - Beide Server mit ufw absichern + +Sperre nun beide Server ab. Wir gehen davon aus, dass `ufw` installiert ist (es ist Teil des im Anforderungsabschnitt verlinkten Basis-Härtungs-Tutorials); falls nicht, installiere es auf beiden mit `sudo apt install ufw`. + +## Schritt 7.1 - Den Edge-Server absichern + +Verweigere auf **s-edge** standardmäßig jeglichen eingehenden Verkehr und erlaube dann nur SSH und die Web-Ports (80 für die Let's-Encrypt-HTTP-Challenge, 443 für pgAdmin über HTTPS): + +```bash +sudo ufw default deny incoming +sudo ufw default allow outgoing +sudo ufw allow OpenSSH +sudo ufw allow 80/tcp +sudo ufw allow 443/tcp +sudo ufw enable +``` + +## Schritt 7.2 - Den Datenbankserver absichern + +Verweigere auf **s-db** jeglichen eingehenden Verkehr und erlaube dann **nur dem Edge-Server**, über das vLAN auf PostgreSQL und SSH zuzugreifen: + +```bash +sudo ufw default deny incoming +sudo ufw default allow outgoing +sudo ufw allow from 10.132.0.10 to any port 22 proto tcp +sudo ufw allow from 10.132.0.10 to any port 5432 proto tcp +sudo ufw enable +``` + +> **Tip** +> Da SSH zu s-db jetzt nur noch vom Edge-Server aus erlaubt ist, erreichst du s-db über den Edge als Jump-Host: `ssh -J operator@ operator@10.132.0.20`. + +# Fazit + +Du hast jetzt einen PostgreSQL-Datenbankserver **ohne öffentliche IP-Adresse**, der nur über ein privates Cloud vLAN erreichbar ist. Die gesamte Administration läuft über einen einzigen, gehärteten Edge-Server: pgAdmin für die Datenbank und ein SSH-Jump-Host für die Shell. Der Edge-Server versorgt die Datenbank außerdem per NAT mit Internetzugang für Updates. Die Angriffsfläche der Datenbank wurde von "das gesamte Internet" auf "einen Server, den du kontrollierst" reduziert. + +Einige mögliche nächste Schritte: + +- Aktiviere TLS zwischen pgAdmin und PostgreSQL, um auch die Verbindung über das vLAN zu verschlüsseln. +- Installiere [Fail2Ban](https://community.netcup.com/de/tutorials/how-to-install-and-configure-fail2ban-on-a-debian-server) auf dem Edge-Server, um Brute-Force-SSH-Angriffe zu blockieren. +- Richte automatisierte PostgreSQL-Backups auf dem Datenbankserver ein. + +# Licence + +[MIT](https://github.com/netcup-community/community-tutorials/blob/main/LICENSE) + +Copyright (c) 2026 netcup + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicence, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# Contributor's Certificate of Origin + +By making a contribution to this project, I certify that: + +1. The contribution was created in whole or in part by me and I have the right to submit it under the licence indicated in the file; or + +2. The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate licence and I have the right under that licence to submit that work with modifications, whether created in whole or in part by me, under the same licence (unless I am permitted to submit under a different licence), as indicated in the file; or + +3. The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. + +4. I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the licence(s) involved.