Setting Up a Multi-Service Server with Security-First Approach

内容分享2小时前发布
0 0 0

Setting Up a Multi-Service Server with Security-First Approach

Introduction

This article will guide you through deploying multiple services using containers and setting up reverse proxies with Nginx Proxy Manager (NPM) to expose these services via different subdomains. We’ll use Uptime Kuma (server monitoring tool) and Heimdall (application dashboard) as examples to demonstrate the complete setup process.

This deployment emphasizes security as the top priority. The following security-focused features are key characteristics of this guide:

Rootless Containers: All containers run in rootless mode to minimize the security attack surface by avoiding root privilegesHTTPS Protection: All services are protected by HTTPS with automatic SSL certificate management through Let’s EncryptMinimal Attack Surface: Firewall configuration ensures only necessary ports (80 and 443) are exposed, with all service ports kept closedSecurity-First Architecture: The entire setup is designed with security as the primary consideration, prioritizing protection over convenience

This guide uses CentOS Stream as an example. The commands and steps are largely compatible with other Red Hat-based distributions such as AlmaLinux, Rocky Linux, and Fedora.

For Ubuntu or Debian-based systems, or if you prefer to use Docker instead of Podman, most steps are similar, but some commands (package manager usage, service names, etc.) will vary. Where applicable, substitute
dnf
with
apt
, and replace
podman
with
docker
.

The core concepts of networking, reverse proxy configuration, and SSL certificate setup remain the same across platforms and container runtimes.

Environment Preparation

Before starting, ensure your system meets the following requirements:

Red Hat Enterprise Linux (RHEL) 8 or higherUser with sudo privilegesSystem connected to the internetAt least one domain name (for configuring subdomains)

Note: This guide is based on deployment on a Cloud Instance. The steps and configurations are applicable to cloud instances from various providers, as well as physical servers and local virtual machines.

System Update

First, update the system packages:


sudo dnf update -y

Installing Podman

Podman is a daemonless container engine that is compatible with Docker but lighter and more secure.

Installing Podman


sudo dnf install -y podman

Note:
If your Podman version is below 4.0 (check with
podman --version
), it may not have built-in
podman compose
functionality. You can install the
podman-compose
package separately for Docker Compose-like functionality.

Verifying Installation


podman --version

Deploying Nginx Proxy Manager

Nginx Proxy Manager (NPM) is a web-based reverse proxy management tool that provides a user-friendly graphical interface for managing Nginx configurations.

Official Reference:
For more details and advanced configuration, please see the Nginx Proxy Manager official documentation.

Preparing Data Directories

Before starting, create the necessary directories for persistent data and SSL certificates:


mkdir -p ~/applications/nginx-proxy-manager/data
mkdir -p ~/applications/nginx-proxy-manager/letsencrypt

Writing the Podman Compose File

Below is an example
container-compose.yml
file for Nginx Proxy Manager, located at
~/applications/nginx-proxy-manager/container-compose.yml
:


services:
  app:
    image: 'docker.io/jc21/nginx-proxy-manager:latest'
    restart: unless-stopped
    ports:
      # These ports are in format <host-port>:<container-port>
      - '0.0.0.0:80:80'   # Public HTTP Port
      - '0.0.0.0:443:443' # Public HTTPS Port
      - '0.0.0.0:81:81'   # Admin Web Port
      # Add any other Stream port you want to expose
      # - '21:21' # FTP
    # environment:
      # Uncomment this if you want to change the location of
      # the SQLite DB file within the container
      # DB_SQLITE_FILE: "/data/database.sqlite"
      # Uncomment this if IPv6 is not enabled on your host
      # DISABLE_IPV6: 'true'
    volumes:
      - ./data:/data:Z
      - ./letsencrypt:/etc/letsencrypt:Z

Note on
0.0.0.0:
:

Specifying
0.0.0.0:
in the port mapping ensures that Nginx Proxy Manager (NPM) is accessible from all network interfaces. This is crucial when you want to serve the application to the internet.

About
:Z
in volume mounting:

The
:Z
suffix is specific to Podman and sets the proper SELinux context on mounted volumes. It is required by Podman to ensure that containers have appropriate permissions to access the host directories. Without
:Z
, you may encounter permission denied errors due to SELinux policies, so it is recommended to always include it when binding host directories into containers under Podman.

Starting the Nginx Proxy Manager Container with Podman Compose

Change to the application directory and start Nginx Proxy Manager using Podman Compose:


cd ~/applications/nginx-proxy-manager
podman compose up -d

Accessing the NPM Management Interface

Open your browser and navigate to:
http://your-server-ip:81

Default login credentials:

Email:
admin@example.com
Password:
changeme

Important: Change the default password immediately after first login!

Deploying Uptime Kuma

Uptime Kuma is an open-source monitoring tool for tracking the availability of websites and services.

Official Reference:
Uptime Kuma Docker Compose Setup (Official)

Preparing Data Directory

Create a directory for persistent Uptime Kuma data:


mkdir -p ~/applications/uptime-kuma/data

Writing the Podman Compose File

Below is an example
container-compose.yml
file for Nginx Proxy Manager, located at
~/applications/uptime-kuma/container-compose.yml
:


services:
  uptime-kuma:
    image: louislam/uptime-kuma:2
    restart: unless-stopped
    volumes:
      - ./data:/app/data:Z
    ports:
      # <Host IP>:<Host Port>:<Container Port>
      - "0.0.0.0:13001:3001"

Starting Uptime Kuma with Podman Compose

From the directory containing your
container-compose.yml
, run:


podman compose up -d

Accessing Uptime Kuma

You can now access Uptime Kuma at
http://your-server-ip:13001
.
The first visit will take you to the initialization wizard.

Deploying Heimdall

Heimdall is a beautiful application dashboard that can consolidate quick access links to all commonly used services.

Official Reference:
Heimdall Docker Compose Setup (Official)

Preparing Data Directory

Create a directory for persistent Heimdall data:


mkdir -p ~/applications/heimdall/heimdall_data/

Writing the Podman Compose File

Below is an example
container-compose.yml
file for Heimdall, located at
~/applications/heimdall/container-compose.yml
:


services:
  heimdall:
    image: lscr.io/linuxserver/heimdall:latest
    container_name: heimdall
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Etc/UTC
      - ALLOW_INTERNAL_REQUESTS=false # optional
    volumes:
      - ./heimdall_data/config:/config:Z
    ports:
      # <Host IP>:<Host Port>:<Container Port>
      - "0.0.0.0:10080:80"
      - "0.0.0.0:10443:443"
    restart: unless-stopped

Starting Heimdall with Podman Compose

From the directory containing your
container-compose.yml
, run:


podman compose up -d

This will start Heimdall in the background.

Accessing Heimdall

You can now access Heimdall at
http://your-server-ip:10080
.

On your first visit, you will be guided through the initialization wizard.

Configuring Subdomain Reverse Proxies

Now we need to bind different services to different subdomains through NPM, eliminating the need to remember port numbers.

Prerequisites

Ensure your domain DNS is configured:

Point
*.yourdomain.com
A record to your server IPOr create individual A records for each subdomain:

npm.yourdomain.com
→ Server IP
uptime.yourdomain.com
→ Server IP
heimdall.yourdomain.com
→ Server IP

Configuring Reverse Proxy in NPM

1. Configuring Uptime Kuma Subdomain

Log in to the NPM management interface (
http://your-server-ip:81
)Click “Proxy Hosts” → “Add Proxy Host”Fill in the following information:

Details:

Domain Names:
uptime.yourdomain.com
Scheme:
http
Forward Hostname / IP:
your-server-lan-ip
(use your server’s local network IP address, e.g.,
192.168.1.100
or
10.0.0.50
)Forward Port:
13001
(matches the port configured in Uptime Kuma’s container-compose.yml)Block Common Exploits: CheckWebsockets Support: Check (required for Uptime Kuma)

SSL:

Check “Request a new SSL Certificate”Check “Force SSL”Check “HTTP/2 Support”Check “Accept Terms of Service”

Advanced Tab (Important for Uptime Kuma):

Click on the “Advanced” tabIn the “Custom Nginx Configuration” section, add the following configuration:


# Custom headers for Uptime Kuma
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;

Why these headers are needed:


X-Forwarded-Proto
: Tells Uptime Kuma that the original request was HTTPS, ensuring proper protocol detection
X-Forwarded-For
: Preserves the original client IP address through the proxy chain
X-Real-IP
: Provides the real client IP address
Host
: Ensures the correct host header is passed to the backend
X-Forwarded-Host
and
X-Forwarded-Port
: Additional information for proper URL generation

Click “Save”

2. Configuring Heimdall Subdomain

Repeat the above steps to configure Heimdall:

Details:

Domain Names:
heimdall.yourdomain.com
Scheme:
http
Forward Hostname / IP:
your-server-lan-ip
(use your server’s local network IP address, same as above)Forward Port:
10080
(matches the port configured in Heimdall’s container-compose.yml)Block Common Exploits: CheckWebsockets Support: Check (optional, not required for Heimdall)

SSL:

Check “Request a new SSL Certificate”Check “Force SSL”Check “HTTP/2 Support”Check “Accept Terms of Service”

Advanced Tab (Optional for Heimdall):

Similar header configuration can be added if needed, though Heimdall typically works fine with default NPM headers

3. Configuring NPM Management Interface Subdomain (Optional)

You can also configure a subdomain for the NPM management interface:

Details:

Domain Names:
npm.yourdomain.com
Scheme:
http
Forward Hostname / IP:
localhost
Forward Port:
81
Block Common Exploits: CheckWebsockets Support: Check (optional, not required for NPM)

SSL:

Check “Request a new SSL Certificate”Check “Force SSL”Check “HTTP/2 Support”Check “Accept Terms of Service”

Note: The NPM management interface should ideally be accessed only from internal networks, or set strong passwords and firewall rules.

Configuring Uptime Kuma Reverse Proxy Settings

After setting up the reverse proxy in NPM, you need to configure Uptime Kuma to trust the proxy headers. This ensures Uptime Kuma correctly detects HTTPS connections and client IP addresses when accessed through the reverse proxy.

Access Uptime Kuma Settings:

Navigate to
https://uptime.yourdomain.com
(or
http://your-server-ip:13001
if not using HTTPS yet)Go to SettingsReverse Proxy

Configure Trust Proxy:

In the “HTTP Headers” section, you’ll find the “Trust Proxy” optionSet “Trust Proxy” to “Yes”This tells Uptime Kuma to trust the
X-Forwarded-*
headers sent by NPM

Why this is important:

When “Trust Proxy” is enabled, Uptime Kuma will:
Use
X-Forwarded-Proto
to detect HTTPS connectionsUse
X-Forwarded-For
or
X-Real-IP
to get the real client IP addressUse
X-Forwarded-Host
to get the correct host information Without this setting, Uptime Kuma might show incorrect URLs (with IP and port) and log wrong client IPs

Save Settings:

Click “Save” to apply the changes

Note: The “Trust Proxy” setting works together with the headers we configured in NPM’s Advanced tab:

NPM’s Advanced tab: Configures which headers to send to Uptime Kuma (
proxy_set_header
directives)Uptime Kuma’s Trust Proxy: Tells Uptime Kuma to trust and use those headers

Both configurations are necessary for Uptime Kuma to work correctly behind the reverse proxy.

Verifying Configuration

After configuration, you should be able to access each service via:


https://uptime.yourdomain.com
– Uptime Kuma
https://heimdall.yourdomain.com
– Heimdall
https://npm.yourdomain.com
– NPM management interface (if configured)

All connections should automatically use HTTPS, with SSL certificates automatically requested and renewed by Let’s Encrypt.

Firewall Configuration

For enhanced security, you should enable your server’s firewall. This protects your server by only allowing necessary network traffic.

Firewall Strategy

Since all your services are proxied through NPM, the firewall configuration follows this principle:

Open ports 80 and 443: Required for NPM to receive HTTP and HTTPS trafficKeep service ports closed: Ports 81 (NPM management), 13001 (Uptime Kuma), and 10080 (Heimdall) should remain closedAccess via HTTPS: All services should be accessed through the reverse proxy using HTTPS subdomains

This approach ensures that:

All services are protected by HTTPS and SSL certificatesThe attack surface is minimized (fewer exposed ports)Access is centralized through NPMBetter security and centralized access control

Configuring Firewall Rules

Open ports 80 (HTTP) and 443 (HTTPS) to allow NPM to receive traffic:


# Open HTTP and HTTPS ports
sudo firewall-cmd --permanent --add-port=80/tcp
sudo firewall-cmd --permanent --add-port=443/tcp
sudo firewall-cmd --reload

Verifying Firewall Configuration

Verify that only the required ports are open and service ports are not exposed:


# Check which ports are currently open
sudo firewall-cmd --list-ports

Expected output: Only
80/tcp
and
443/tcp
should be listed.

You should NOT see:


81/tcp
(NPM management interface – should be accessed via
https://npm.yourdomain.com
if reverse proxy is configured)
13001/tcp
(Uptime Kuma – should be accessed via
https://uptime.yourdomain.com
)
10080/tcp
(Heimdall – should be accessed via
https://heimdall.yourdomain.com
)

If you find any of these service ports in the allowed ports list, remove them for better security:


# Remove service ports if they are exposed (only if they appear in the list)
sudo firewall-cmd --permanent --remove-port=81/tcp
sudo firewall-cmd --permanent --remove-port=13001/tcp
sudo firewall-cmd --permanent --remove-port=10080/tcp
sudo firewall-cmd --reload

Accessing NPM Management Interface

If you have configured a reverse proxy for the NPM management interface, access it via
https://npm.yourdomain.com
. Port 81 should remain closed in the firewall.

If the NPM reverse proxy is not working, or if you haven’t configured a reverse proxy for the management interface, you can temporarily open port 81 to access the NPM management interface directly:


# Temporarily open port 81 for direct access to NPM management interface
sudo firewall-cmd --add-port=81/tcp
sudo firewall-cmd --reload

After accessing the management interface at
http://your-server-ip:81
, remember to close port 81 for security:


# Close port 81 after use
sudo firewall-cmd --remove-port=81/tcp
sudo firewall-cmd --reload

Security Best Practice: It is strongly recommended to configure a reverse proxy for the NPM management interface and access it via HTTPS (
https://npm.yourdomain.com
) rather than exposing port 81 directly. This provides encryption and better security.

Troubleshooting

If you temporarily cannot access services, you can temporarily disable the firewall for debugging purpose (not recommended for production):


sudo systemctl stop firewalld  # Temporarily disable for debugging
# Remember to re-enable it after debugging:
sudo systemctl start firewalld

Appendix: Understanding Container Networking

Note: The following section provides in-depth knowledge about container networking and explains the technical reasoning behind the configuration choices made in this tutorial. This is supplementary information for readers who want to understand the “why” behind the configuration steps.

Forward Hostname / IP Configuration in Reverse Proxies

When configuring reverse proxies in NPM, you need to specify the “Forward Hostname / IP” address for your backend services. This section explains why using your server’s local network IP address is the recommended approach, and why other options may not work in containerized environments.

Why Use the Local Network IP Address?

When configuring reverse proxies in NPM, you should use your server’s local network IP address (e.g.,
192.168.1.100
or
10.0.0.50
) for the “Forward Hostname / IP” field. This allows NPM to access services mapped to the host ports reliably, especially in rootless Podman environments.

Finding Your Local Network IP:


# Find your server's local network IP address
ip addr show | grep "inet " | grep -v "127.0.0.1"
# Or
hostname -I

Why Not
localhost
?

In NPM,
localhost
refers to the NPM container’s loopback interface, not the container host’s loopback interface. Since the services (Uptime Kuma, Heimdall) are running on the host with port mappings, NPM cannot reach them via
localhost
from within the container.

Key Point: Containers have isolated network namespaces. When you use
localhost
inside a container, it refers to the container’s own network interface, not the host’s interface.

Why Not
host.containers.internal
?

While
curl http://host.containers.internal:13001
may work from within the container when testing manually, Nginx (OpenResty) – which powers NPM – runs in a different namespace context. This can cause issues for several reasons:

Namespace Context Differences: The socket used by
curl
(user-space socket) is not available to the Nginx process running in the container’s namespaceRootless Podman Limitations: In rootless Podman environments with
slirp4netns
, containers may not reliably access the host IP via DNS aliases like
host.containers.internal
Network Resolution: DNS-based resolution may not work consistently across different container runtime contexts

Key Point: Just because a command works in one context (like
curl
from a shell) doesn’t mean it will work for all processes (like Nginx) running in different namespace contexts.

Why Not the Public Server IP?

While technically possible, using the public server IP address is not recommended for security reasons:

Security Concerns: We don’t want to expose backend service ports directly to the internetHTTPS Termination: HTTPS termination happens at NPM, while backend services expose unencrypted HTTPAttack Surface: Using the public IP would expose these unencrypted services to the internet, significantly reducing securityNetwork Isolation: The firewall configuration blocks external access to service ports, which is a security best practice

Key Point: Even though the firewall blocks external access, using the public IP can create unnecessary network paths and potential security risks.

Rootless Podman and Network Isolation

In rootless Podman mode, the networking configuration becomes more complex:


slirp4netns
: Podman uses
slirp4netns
(userspace network virtualization) in rootless modeDNS Resolution: Container network DNS resolution (
dnsname
) is not availableDynamic IPs: Container IPs are dynamically assigned by
slirp4netns
and may change on restartStable Port Mappings: However, port mappings (hostPort → containerPort) remain stable and reliableSecurity Priority: This configuration prioritizes security by not using root-mode containers and networks

The complexity of this configuration is due to rootless container limitations, but this deployment prioritizes security over convenience. By using rootless containers, we avoid running containers with root privileges, which significantly reduces the security attack surface.

Why This Configuration Works

Using the host’s local network IP address in the NPM reverse proxy configuration works because:

Direct Host Access: NPM can directly access services mapped to host ports via the host’s network interfaceReliability: This is the most reliable method, especially in rootless Podman environmentsSecurity: Combined with firewall configuration (which blocks external access to service ports), this ensures security while maintaining functionalityConsistency: The host’s local network IP remains stable across container restarts

Future Considerations

As Podman continues to evolve, newer versions may provide more convenient and secure solutions for container-to-host networking. However, as of Podman 4.x, using the host’s local network IP address remains one of the most reliable and secure implementation methods for this type of deployment.

Key Takeaway: Understanding container networking isolation is crucial when configuring reverse proxies. The local network IP address provides a reliable bridge between containerized services and host-mapped ports while maintaining security boundaries.

© 版权声明

相关文章

暂无评论

none
暂无评论...