bolt Valebyte VPS from $4/mo — NVMe, 60s deploy.

Get a VPS arrow_forward
eco Beginner Tutorial/How-to

Deploying Caddy as

calendar_month Aug 07, 2026 schedule 22 min read visibility 75 views
Развёртывание Caddy как обратного прокси для Docker-контейнеров на VPS с автоматическим HTTPS
info

Need a server for this guide? We offer dedicated servers and VPS in 50+ countries with instant setup.

Need a server for this guide?

Deploy a VPS or dedicated server in minutes.

Deploying Caddy as a Reverse Proxy for Docker Containers on a VPS with Automatic HTTPS

TL;DR

In this detailed guide, we will configure Caddy as a highly efficient reverse proxy server for your Docker containers on a Virtual Private Server (VPS), ensuring automatic acquisition and renewal of SSL/TLS certificates via Let's Encrypt. You will learn how to deploy and link Docker, Docker Compose, and Caddy to provide secure and scalable access to your web applications running in containers, with minimal configuration effort.

  • Installation and basic configuration of the operating system (Ubuntu 24.04/26.04 LTS).
  • Deployment of Docker and Docker Compose for container management.
  • Installation and configuration of Caddy as a reverse proxy.
  • Caddy configuration for automatic HTTPS certificate issuance.
  • Example of deploying an application in Docker and proxying it through Caddy.
  • Recommendations for security, backups, and system maintenance.

What We Are Configuring and Why

Diagram: What We Are Configuring and Why
Diagram: What We Are Configuring and Why

In this guide, we will focus on creating a reliable and secure infrastructure for hosting web applications on your own VPS. The main task is to provide external access to applications running inside Docker containers via a domain name, while automatically securing the connection with HTTPS. To achieve this, we will use a combination of three key components:

  • Docker: A platform for application containerization. Docker allows packaging applications with all their dependencies into isolated "containers," ensuring their portability and predictable operation in any environment. We will use Docker Compose to orchestrate multiple containers that make up a single application or service.
  • Caddy: A modern web server and reverse proxy. Caddy stands out for its configuration simplicity and, most importantly, built-in support for automatic issuance and renewal of SSL/TLS certificates via Let's Encrypt. This eliminates the need to manually configure Certbot or other HTTPS tools, significantly simplifying the process.
  • VPS (Virtual Private Server): A Virtual Private Server that will provide us with the necessary computing power and network resources to host our infrastructure.

Ultimately, the reader will have a fully configured system capable of securely and efficiently hosting one or more web applications (e.g., GitLab, Mattermost, WordPress, or custom SaaS applications) in Docker containers, accessible via domain names with automatic HTTPS. This is an ideal solution for developers, startups, and enthusiasts who want full control over their infrastructure without the high costs of cloud-managed services.

Alternatives: Cloud-managed vs. Self-hosted on VPS

There are two main approaches to deploying web applications:

  • Cloud-managed services: Platforms such as Heroku, Netlify, AWS Elastic Beanstalk, Google App Engine offer high automation, scalability, and minimal administration effort. You simply upload your code, and the platform takes care of servers, scaling, databases, and HTTPS.
    Advantages: Simplicity, high availability, automatic scaling.
    Disadvantages: High cost with growth, less control over infrastructure, vendor lock-in, sometimes limited customization options.
  • Self-hosted on VPS/Dedicated server: This approach involves renting a "bare metal" server (virtual or physical) and independently configuring the entire stack — operating system, web server, database, containerization, etc.
    Advantages: Full control over every aspect of the system, significantly lower cost in the long term (especially under moderate loads), flexibility in technology and configuration choices, data privacy.
    Disadvantages: Requires technical knowledge and time for setup and maintenance; responsibility for security and backups rests with you.

Choosing self-hosted on a VPS with Caddy and Docker is a golden mean for those seeking a balance between control, cost, and ease of use. Caddy significantly lowers the entry barrier for self-configuring HTTPS, and Docker simplifies application management.

What VPS Configuration is Needed for This Task

Diagram: What VPS Configuration is Needed for This Task
Diagram: What VPS Configuration is Needed for This Task

Choosing the right VPS configuration is critical for the stable operation of your applications. Requirements will depend on the number and resource intensity of the Docker containers you plan to run.

Minimum Requirements for a Basic Setup (Caddy + 1-2 Light Containers)

  • CPU: 1-2 cores (e.g., Intel Xeon E3/E5 or AMD EPYC). For most web applications, one core is sufficient, but two will provide a performance buffer and better responsiveness.
  • RAM: 2 GB. Docker and Caddy themselves consume little, but each application in a container requires its own memory. 2 GB is a comfortable minimum for the OS, Docker daemon, and a couple of undemanding services (e.g., a small WordPress blog without a heavy DBMS or a simple API service).
  • Disk: 40-60 GB NVMe SSD. SSD significantly speeds up I/O operations, which is important for databases and fast container startup. NVMe offers even greater speed. 40-60 GB is sufficient for the OS, Docker images, containers, and a small amount of data. If large volumes of data are planned (e.g., file storage, logs, backups), more space will be required.
  • Network: 100-200 Mbps. For most tasks, this is more than enough. Channel stability and low latency are more important.

Specific VPS Plan for the Task

For deploying Caddy with 3-5 Docker containers (e.g., GitLab, Mattermost, or several microservices), the following configuration plan is recommended:

  • CPU: 2-4 cores (e.g., Intel Xeon E5-2690v4 or AMD EPYC 7002 series). This will provide sufficient performance for handling requests and background container tasks.
  • RAM: 4-8 GB. For more demanding applications such as GitLab, Mattermost, or a Minecraft server, 4 GB is the minimum; 8 GB will provide significantly more flexibility and stability.
  • Disk: 80-160 GB NVMe SSD. GitLab, Minecraft, and other similar applications can occupy significant disk space. NVMe SSD will ensure high-speed data operations.
  • Network: 500 Mbps - 1 Gbps. High bandwidth will be beneficial for applications with a large number of users or intensive traffic.

You can consider a VPS with the specified characteristics to get an optimal price-to-performance ratio for this task. Ensure that the chosen plan includes a public IPv4 address, which is necessary for accessing your services from the internet and for Let's Encrypt to function.

When a Dedicated Server is Needed, Not a VPS

A dedicated server becomes the preferred choice when:

  • High performance and stability: Maximum and predictable performance is required without "noisy neighbors" on a single physical server.
  • Large data volumes: Hundreds of gigabytes or terabytes of data are planned for storage.
  • Complex computations: Specialized processors (e.g., GPUs for machine learning) or a very large number of cores are needed.
  • Strict security/compliance requirements: Full physical control over the hardware is required.
  • Massive traffic: Very high network traffic is expected (tens of TB per month).

For most tasks described in this guide (GitLab for a team, Mattermost, Minecraft for friends), a powerful VPS will suffice. However, if you are launching a large SaaS project with thousands of users or a high-load crypto node, consider a suitable dedicated server.

Location: What it Affects

The choice of VPS location has several key aspects:

  • Latency: The closer the server is to your primary audience, the lower the latency will be, and the faster pages will load for end-users. For a European audience, choose a server in Europe; for an American audience, choose one in North America.
  • Regulatory requirements: In some jurisdictions, there are strict data storage laws (e.g., GDPR in the EU). The choice of server location can affect compliance with these requirements.
  • Cost: VPS prices may vary slightly depending on the region.

For most tasks, if your audience is distributed, choose a location that is geographically close to you or your primary user group.

Server Preparation

Diagram: Server Preparation
Diagram: Server Preparation

After gaining access to your new VPS, you need to perform a series of initial configurations to ensure security and ease of use. We will be using Ubuntu Server 24.04/26.04 LTS as the operating system.

It is assumed that you have accessed the server via SSH as the root user or another user with sudo privileges.

1. System Update

First, let's update all packages to their latest versions.


sudo apt update          # Updates the list of available packages
sudo apt upgrade -y      # Upgrades all installed packages
sudo apt autoremove -y   # Removes unnecessary packages

2. Creating a New User with Sudo Privileges (if you are working as root)

Working as the root user is insecure. Let's create a new user and grant them sudo privileges.


sudo adduser username    # Replace 'username' with your desired username. Follow the prompts to create a password.
sudo usermod -aG sudo username # Adds the user to the sudo group to grant administrator privileges.

Now, exit the current SSH session (exit) and log in as the new user:


ssh username@your_vps_ip # Replace 'username' and 'your_vps_ip'

3. Configuring SSH Keys for Secure Access

Using SSH keys is much more secure than passwords. If you don't already have an SSH key, generate one on your local machine:


ssh-keygen -t ed25519 -C "[email protected]" # Creates a new SSH key (ed25519 is more secure)

Then, copy the public key to your VPS:


ssh-copy-id username@your_vps_ip # Copies the public key to the server

Now you can log in to the server without a password. After verifying that SSH keys work, it is recommended to disable password login for root and your new user, and also to disallow root login.


sudo nano /etc/ssh/sshd_config # Opens the SSH server configuration file

Find and change the following lines:


PermitRootLogin no
PasswordAuthentication no

Save changes (Ctrl+X, Y, Enter) and restart the SSH server:


sudo systemctl restart sshd # Restarts the SSH service

Important: Make sure you can log in with an SSH key before disabling password login! Otherwise, you might lose access to the server.

4. Installing and Configuring a Firewall (UFW)

UFW (Uncomplicated Firewall) is an easy-to-use interface for configuring iptables. It is necessary to restrict access to the server only to the required ports.


sudo apt install ufw -y # Installs UFW
sudo ufw allow OpenSSH  # Allows SSH (port 22)
sudo ufw allow http     # Allows HTTP (port 80)
sudo ufw allow https    # Allows HTTPS (port 443)
sudo ufw enable         # Enables UFW. Confirm with 'y'.
sudo ufw status verbose # Checks UFW status

5. Installing Fail2ban for Brute-Force Protection

Fail2ban scans service logs (SSH, web servers, etc.) for suspicious activity (e.g., multiple failed login attempts) and temporarily or permanently blocks attackers' IP addresses using firewall rules.


sudo apt install fail2ban -y # Installs Fail2ban
sudo systemctl enable fail2ban # Enables Fail2ban to start automatically on boot
sudo systemctl start fail2ban  # Starts the Fail2ban service
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local # Creates a local copy of the config for modifications
sudo nano /etc/fail2ban/jail.local # Edits the configuration

In the jail.local file, you can configure parameters such as bantime (block time), findtime (period during which failed attempts are counted), and maxretry (maximum number of attempts). Make sure the [sshd] section is enabled (enabled = true). You can increase bantime, for example, to 1h or even 1d.


[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5

[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
backend = %(sshd_backend)s

Save changes and restart Fail2ban:


sudo systemctl restart fail2ban # Restarts Fail2ban to apply changes
sudo fail2ban-client status sshd # Checks SSH protection status

Software Installation — Step-by-Step

Diagram: Software Installation — Step-by-Step
Diagram: Software Installation — Step-by-Step

Now that the server is prepared, let's proceed with installing the main software: Docker, Docker Compose, and Caddy.

1. Installing Docker Engine (relevant for 2026)

We will install Docker from the official Docker repository to always have the latest versions. Current Docker Engine versions for 2026 will likely be in the 25.x-26.x range.


# Updates packages and installs dependencies
sudo apt update && sudo apt install -y ca-certificates curl gnupg lsb-release

# Adds Docker's official GPG key
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

# Adds the Docker repository to APT sources
echo \
  "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# Updates package lists with the new repository
sudo apt update

# Installs Docker Engine, Docker CLI, and containerd
sudo apt install -y docker-ce docker-ce-cli containerd.io

# Adds the current user to the docker group to work without sudo
sudo usermod -aG docker "$USER"

# Applies changes for the Docker group (requires re-login or reboot)
# But it's better to just re-login or run 'newgrp docker'
newgrp docker

# Verifies Docker installation
docker run hello-world

The docker run hello-world command should successfully download and run a test container, confirming the correct Docker installation.

2. Installing Docker Compose

Docker Compose is a tool for defining and running multi-container Docker applications. It comes as part of Docker Engine since version 2.x.


# Checks Docker Compose version (Docker Compose v2.x is shipped as a Docker CLI plugin)
docker compose version

If the docker compose version command returns an error or a very old version, you might need to install it separately. However, for Ubuntu 24.04/26.04 and Docker Engine 25.x+, it should be available by default.

3. Installing Caddy (relevant for 2026)

Caddy can be installed from the official Caddy repository, which ensures you get the latest stable versions (Caddy 2.x).


# Installs dependencies for adding the HTTPS repository
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https

# Adds Caddy's GPG key
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg

# Adds the Caddy repository to APT sources
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list

# Updates package lists with the new repository
sudo apt update

# Installs Caddy
sudo apt install -y caddy

# Checks Caddy service status
sudo systemctl status caddy

The Caddy service should be installed and running. If it's not running, start it: sudo systemctl start caddy.

Configuration

Diagram: Configuration
Diagram: Configuration

Now that all components are installed, it's time to configure their interaction. We will set up Caddy as a reverse proxy for Docker containers, using Docker Compose to define our applications.

1. Preparing Docker Network Infrastructure

For Caddy to interact with your Docker containers using their internal names, they need to be placed in a single custom Docker network.


# Create a custom Docker network for Caddy and applications
docker network create caddy_network

We will specify this network in the docker-compose.yml files for our applications and for Caddy itself.

2. Caddyfile Configuration

Caddy uses the file Caddyfile for its configuration. We will set it up to proxy traffic to our Docker containers.


sudo nano /etc/caddy/Caddyfile # Open the main Caddy configuration file

Remove the existing content and paste the following. Replace your-app.example.com and another-app.example.com with your actual domain names, and app_container_name and another_app_container_name with the names of your Docker containers.


# Global Caddy settings
{
    # Specifies that Caddy should use Google DNS servers for Let's Encrypt
    # This can be useful if your VPS has issues resolving DNS
    # acme_dns google # Uncomment if you are using a DNS provider that supports ACME DNS challenge
    # email [email protected] # Specify your email for Let's Encrypt notifications
}

# Configuration for the first application
your-app.example.com {
    # Proxy all traffic to the Docker container
    reverse_proxy app_container_name:80

    # Additional headers for better compatibility and security
    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains"
        X-Frame-Options DENY
        X-Content-Type-Options nosniff
        X-XSS-Protection "1; mode=block"
    }

    # Request logging
    log {
        output file /var/log/caddy/your-app.log
        format json
    }

    # Gzip compression (optional, but recommended)
    encode gzip
}

# Configuration for the second application (example)
another-app.example.com {
    reverse_proxy another_app_container_name:8080 # Specify the port your second application is listening on

    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains"
        X-Frame-Options DENY
        X-Content-Type-Options nosniff
        X-XSS-Protection "1; mode=block"
    }

    log {
        output file /var/log/caddy/another-app.log
        format json
    }

    encode gzip
}

# You can also add a directive for static files if Caddy will serve them
# example.com {
#     root  /var/www/html
#     file_server
# }

Caddyfile Explanations:

  • your-app.example.com: This is the domain name through which your application will be accessible. Caddy will automatically obtain an HTTPS certificate for it. Ensure that the DNS record (A-record) for this domain points to your VPS's IP address.
  • reverse_proxy app_container_name:80: Caddy will redirect all incoming requests to the internal IP address of the Docker container named app_container_name on port 80 (or another port your application is listening on inside the container). It is important that app_container_name is the service name in your docker-compose.yml file.
  • header: Adds HTTP headers to enhance security.
  • log: Configures request logging. Ensure that the directory /var/log/caddy/ exists and Caddy has permissions to it.
  • encode gzip: Enables Gzip compression for transmitted data.

Let's create a directory for Caddy logs:


sudo mkdir -p /var/log/caddy
sudo chown caddy:caddy /var/log/caddy # Grant permissions to the caddy user
sudo systemctl restart caddy # Restart Caddy to apply the new Caddyfile
sudo systemctl status caddy  # Check that Caddy started successfully

3. Example Application Deployment with Docker Compose

Let's create an example of a simple web application (e.g., Nginx with a test page) that will be accessible via Caddy.


mkdir ~/my_web_app
cd ~/my_web_app
nano docker-compose.yml

Contents of docker-compose.yml:


version: '3.8'

services:
  # Service name that Caddy will use for proxying
  # Ensure it matches the name in the Caddyfile (e.g., 'app_container_name')
  mywebapp:
    image: nginx:latest # Use the latest Nginx image
    container_name: mywebapp_container # Explicit container name
    restart: unless-stopped
    ports:
      - "80" # Nginx listens on port 80 inside the container
    networks:
      - caddy_network # Connect the container to the Caddy network

networks:
  caddy_network:
    external: true # Specify that the network already exists and was created manually

Save the file (Ctrl+X, Y, Enter) and start the application:


docker compose up -d # Start the container in detached mode

Now, edit your Caddyfile to proxy to mywebapp:


sudo nano /etc/caddy/Caddyfile

Add or modify the section for your domain:


# ... other settings ...

# Configuration for your new application
my-nginx-app.example.com { # Replace with your actual domain
    reverse_proxy mywebapp:80 # Service name from docker-compose.yml and internal port
    log {
        output file /var/log/caddy/my-nginx-app.log
        format json
    }
    encode gzip
}

# ... remaining settings ...

Restart Caddy:


sudo systemctl reload caddy # Reload Caddy configuration without stopping the service

Ensure that the DNS record for my-nginx-app.example.com points to your VPS's IP address. Within a few seconds (or minutes, while Caddy obtains the certificate), your application should be accessible via HTTPS.

4. Configuring Environment Variables (.env) for Secrets

Never store sensitive data (passwords, API keys) directly in docker-compose.yml. Use environment variables via a .env file.

Create a .env file in the same directory as docker-compose.yml:


nano ~/my_web_app/.env

Example .env content:


DB_PASSWORD=MyStrongPassword123
API_KEY=supersecretkeyabc123

Then, in docker-compose.yml, you can reference these variables:


version: '3.8'

services:
  mywebapp:
    image: mycustomapp:latest
    container_name: mywebapp_container
    restart: unless-stopped
    environment:
      - DB_PASSWORD=${DB_PASSWORD} # Pass variable from .env
      - API_KEY=${API_KEY}
    networks:
      - caddy_network

networks:
  caddy_network:
    external: true

When running docker compose up -d, Docker Compose will automatically substitute values from .env.

5. Verifying Functionality

After all configurations, it is necessary to ensure that everything is working correctly.

  • Caddy Check:
    
    sudo systemctl status caddy # Ensure Caddy is running
    sudo journalctl -u caddy --since "10 minutes ago" # Check Caddy logs for errors
    
  • Docker Container Check:
    
    docker ps # Ensure your containers are running
    docker logs mywebapp_container # Check logs of a specific container
    
  • Domain Accessibility Check:

    On your local machine, use curl to check the HTTPS connection:

    
    curl -vI https://my-nginx-app.example.com # Check HTTP headers and SSL certificate status
    

    You should see an HTTP/2 200 status and information about the SSL certificate issued by Let's Encrypt. If there are issues, ensure that the DNS record is correct and ports 80/443 are open in UFW.

Backups and Maintenance

Diagram: Backups and Maintenance
Diagram: Backups and Maintenance

Regular backups and timely server maintenance are critically important for the stability and security of your infrastructure.

1. What to Back Up

For Docker containers and Caddy, the following data needs to be backed up:

  • Docker Volumes: This is the most crucial part. All persistent data for your applications (databases, uploaded files, user data) should be stored in Docker Volumes. Make sure you correctly map them from containers to the host system.
  • Caddy Configuration Files: The /etc/caddy/Caddyfile file and any other files it references.
  • Docker Compose Configuration Files: The docker-compose.yml files for all your applications.
  • Caddy SSL Certificates: Caddy stores its certificates in /var/lib/caddy/.local/share/caddy/. Although Caddy will automatically restore them, a backup can speed up recovery.
  • System Configurations: SSH configuration, UFW rules, Fail2ban settings.

2. Simple Auto-Backup Script

We will create a simple script that will archive important data and send it to a secure location. For example, we will use tar for archiving and rsync for copying to a remote server. For more reliable and incremental backups, consider restic or borgbackup.

Create a directory for scripts and the script itself:


mkdir -p ~/scripts
nano ~/scripts/backup_script.sh

Contents of backup_script.sh (replace /path/to/your/docker_volumes and user@remote_backup_server:/path/to/backups with your values):


#!/bin/bash

# Settings
BACKUP_DIR="/var/backups/my_vps"
DATA_TO_BACKUP="/path/to/your/docker_volumes /etc/caddy /etc/ssh /etc/ufw /etc/fail2ban /home/username/my_web_app"
REMOTE_BACKUP_TARGET="user@remote_backup_server:/path/to/backups" # S3-compatible storage or another VPS

# Create backup directory if it doesn't exist
mkdir -p "$BACKUP_DIR"

# Backup file name
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILENAME="vps_backup_${TIMESTAMP}.tar.gz"
FULL_BACKUP_PATH="${BACKUP_DIR}/${BACKUP_FILENAME}"

echo "Starting backup at ${TIMESTAMP}..."

# Create archive
sudo tar -czf "$FULL_BACKUP_PATH" $DATA_TO_BACKUP

if [ $? -eq 0 ]; then
    echo "Backup archive created: $FULL_BACKUP_PATH"
    # Copy archive to remote server
    rsync -avz --remove-source-files "$FULL_BACKUP_PATH" "$REMOTE_BACKUP_TARGET"

    if [ $? -eq 0 ]; then
        echo "Backup successfully transferred to remote: $REMOTE_BACKUP_TARGET"
        # Clean up old backups (e.g., keep the last 7)
        find "$BACKUP_DIR" -type f -name "*.tar.gz" -mtime +7 -delete
        echo "Old local backups cleaned up."
    else
        echo "ERROR: Failed to transfer backup to remote."
    fi
else
    echo "ERROR: Failed to create backup archive."
fi

echo "Backup process finished."

Make the script executable:


chmod +x ~/scripts/backup_script.sh

Configure cron to automatically run the script (e.g., daily at 3:00 AM):


crontab -e # Open crontab for the current user

Add the following line to the end of the file:


0 3 * * * /home/username/scripts/backup_script.sh >> /var/log/backup.log 2>&1

This will run the script every day at 3:00 AM and log the output to /var/log/backup.log.

3. Where to Store Backups

Never store backups on the same server as the original data. This is critically important. Options:

  • External S3-compatible storage: Cloud storage services like Amazon S3, DigitalOcean Spaces, Backblaze B2, Yandex Object Storage offer reliable and cost-effective storage. Tools like rclone or restic can work directly with S3.
  • Separate VPS: An inexpensive VPS with a large disk, used exclusively for storing backups. Access via SSH/SCP/RSync.
  • Local Storage: If it's a home server, you can use network-attached storage (NAS).

4. Updates: Rolling vs. Maintenance Window

Keeping software up-to-date is important for security and performance.

  • Rolling Updates: Suitable for high-availability systems with multiple application instances. Updates occur sequentially, without stopping the entire service. This is usually not applicable for a single VPS.
  • Maintenance Window: The standard approach for single servers. A time is chosen (usually at night or on weekends) when the load is minimal, and updates are performed.
    1. OS Updates: Monthly or every few months:
      
      sudo apt update && sudo apt upgrade -y
      sudo reboot # Reboot after updating the kernel or important system components
                          
    2. Docker Image Updates: Every few weeks or as new versions of your applications are released:
      
      cd ~/my_web_app # Navigate to the directory containing docker-compose.yml
      docker compose pull # Download new image versions
      docker compose up -d # Recreate containers with new images
                          
    3. Caddy Updates: Caddy is updated along with system packages (sudo apt upgrade). After updating Caddy, always check its status and logs.

Always perform a backup before major updates or configuration changes.

Troubleshooting + FAQ

This section collects typical problems and frequently asked questions that may arise when deploying Caddy and Docker.

What to do if Caddy doesn't start or fails to obtain a certificate?

Problem: Caddy doesn't start, or you receive a "TLS handshake error" / "certificate provisioning failed" error.

What to check:

  1. Caddy Logs: This is the first thing to check.
    
    sudo journalctl -u caddy --since "5 minutes ago"
                
    Look for errors related to Caddyfile syntax or Let's Encrypt issues.
  2. DNS Records: Ensure that your domain's A-record (e.g., my-nginx-app.example.com) points to your VPS's public IP address. You can check this using dig or nslookup:
    
    dig +short my-nginx-app.example.com
                
  3. Open Ports: Ensure that ports 80 (HTTP) and 443 (HTTPS) are open in your firewall (UFW) on the VPS. Let's Encrypt uses port 80 for the HTTP-01 challenge.
    
    sudo ufw status verbose
                
  4. Caddyfile Syntax: Use the command to check Caddyfile syntax:
    
    sudo caddy validate --config /etc/caddy/Caddyfile
                
  5. Occupied Ports: Ensure that no other process is listening on ports 80 or 443.
    
    sudo lsof -i :80
    sudo lsof -i :443
                

How to fix: Correct the DNS record, open ports in UFW, and fix the Caddyfile syntax according to the logs. If Let's Encrypt reports a rate limit error, wait for some time (usually an hour) and try again.

Docker container doesn't start or is inaccessible via Caddy.

Problem: The container doesn't start, or Caddy cannot reach it (502 Bad Gateway error).

What to check:

  1. Container Status:
    
    docker ps -a # Shows all containers (running and stopped)
                
    If the container is not running, check its logs:
    
    docker logs mywebapp_container
                
    Look for errors that might indicate issues with the application inside the container.
  2. Docker Network: Ensure that the container and Caddy are in the same Docker network (caddy_network).
    
    docker network inspect caddy_network
                
    Look for your container and the Caddy container in the Containers list.
  3. Service Name and Port: Ensure that the service name in Caddyfile (e.g., mywebapp) exactly matches the service name in docker-compose.yml, and that the specified port (e.g., :80) corresponds to the port on which the application is listening inside the container.

How to fix: Correct errors in docker-compose.yml, restart the container. Verify that the service name and port in Caddyfile are correct and reload Caddy.

What is the minimum suitable VPS configuration?

For Caddy and one or two lightweight Docker containers (e.g., a personal blog, a small API), a VPS with 1 CPU core, 2 GB RAM, and 40-60 GB NVMe SSD disk will be minimally suitable. This is sufficient for stable operation of basic services, but with increased load or the addition of more resource-intensive applications (e.g., databases, game servers), an upgrade will be required.

What to choose — VPS or dedicated for this task?

For most tasks related to deploying Caddy and Docker containers (personal projects, small teams, test environments), a VPS is the optimal choice due to its flexibility, scalability, and low cost. A dedicated server becomes justified if you require maximum and predictable performance, very large amounts of disk space, specific hardware (e.g., GPU), or strict isolation and compliance requirements. Start with a VPS and consider upgrading to a dedicated server if requirements significantly increase.

How to update Docker or Caddy?

To update Docker and Caddy installed from official repositories, use the standard package manager commands:


sudo apt update          # Update package lists
sudo apt upgrade -y      # Upgrade all installed packages, including Docker and Caddy
sudo systemctl restart docker # Restart Docker after update
sudo systemctl restart caddy  # Restart Caddy after update

It is recommended to perform these actions during a "maintenance window" and create backups beforehand.

Can I use Caddy for multiple domains?

Yes, Caddy is excellent for hosting multiple domains on a single VPS. Simply add new configuration blocks to your Caddyfile for each domain, specifying which Docker container to proxy traffic to. Caddy will automatically handle issuing HTTPS certificates for all specified domains. Don't forget to update DNS records for each new domain to point to your VPS.


# ...
your-first-app.example.com {
    reverse_proxy first_app_container:80
}

your-second-app.example.com {
    reverse_proxy second_app_container:8080
}
# ...

How to add basic authentication (username/password) via Caddy?

Caddy supports basic HTTP authentication. You can add it to your domain's block in the Caddyfile:


my-protected-app.example.com {
    reverse_proxy protected_app_container:80
    basicauth / {
        username JDUyJDEwJEQ1R015VjR2bVluYmF1Wk9kSjF4dC5tT1I0L2d6LzZqRTlRSDh3c3B0ZlJ1c05xV0o0NEdwYQ== # Password 'SecurePassword123'
    }
}

Replace username and the password hash with your own. The hash can be generated using the command caddy hash-password --plaintext "Your_Secure_Password". This is useful for protecting internal tools or admin panels.

Conclusions and Next Steps

Diagram: Conclusions and Next Steps
Diagram: Conclusions and Next Steps

We have successfully deployed Caddy as a reverse proxy for Docker containers on a VPS, ensuring automatic HTTPS certificate acquisition. This setup provides a powerful, flexible, and secure foundation for hosting your web applications, combining Caddy's simplicity with Docker's isolation and portability.

You now have full control over your infrastructure and can easily add new applications, update existing ones, and ensure their security. This is an excellent starting point for anyone who wants to manage their online services independently.

Next steps for developing your infrastructure:

  • Monitoring and Logging: Integrate monitoring systems (e.g., Prometheus + Grafana) to track server and container status. Set up centralized logging (e.g., ELK Stack or Loki + Grafana) for effective analysis of all your application logs.
  • CI/CD Pipelines: Automate the deployment of your applications using CI/CD systems (e.g., GitLab CI, GitHub Actions, Jenkins). This will allow you to deliver new code to production faster and more reliably.
  • Increased Fault Tolerance and Scaling: For high-load projects, consider using Docker Swarm or Kubernetes for container orchestration across multiple VPS, ensuring high availability and horizontal scaling.

Was this guide helpful?

Your feedback helps us improve our guides.

Share this post:

Send this guide to someone who may find it useful.

Telegram VKVK WhatsApp Facebook LinkedIn XX

Caddy reverse proxy deployment for Docker containers on VPS with automatic HTTPS
support_agent
Valebyte Support
Usually replies within minutes
Hi there!
Send us a message and we'll reply as soon as possible.