---
title: "Hostinger VPS: Reverse Proxy with Caddy"
slug: "hostinger-vps-reverse-proxy-caddy"
category: "ops"
tags: ["ops", "hostinger", "vps", "caddy", "reverse-proxy", "https", "tls", "linux"]
status: "stable"
last_updated: 2026-05-14
summary: "Configure Caddy as a reverse proxy on a Hostinger VPS: Caddyfile syntax, automatic HTTPS, multi-site patterns, and Cloudflare origin compatibility."
related: ["[[ops/hostinger-vps]]", "[[ops/hostinger-vps-hardening]]", "[[ops/hostinger-vps-deploy-systemd]]", "[[ops/cloudflare]]", "[[comparisons/nginx-vs-caddy]]"]
---

## Overview

Caddy is the right reverse proxy for a single-app Hostinger VPS. It provisions and renews Let's Encrypt certificates automatically, reloads on config change without dropping connections, and has a readable config format. Reach for nginx when you need fine-grained upstream controls or stream proxying. See [[comparisons/nginx-vs-caddy]] for the full tradeoff. This page covers installation, the Caddyfile patterns you will use most, and how to align Caddy with a [[ops/cloudflare]] front-end.

## Install Caddy from the official repository

Do not install Caddy from the default Ubuntu `apt` repository; that package is often years old.

```bash
apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \
  | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \
  | tee /etc/apt/sources.list.d/caddy-stable.list
apt update && apt install -y caddy
systemctl enable --now caddy
```

Caddy runs as a dedicated `caddy` user and stores its data (certs, ACME state) in `/var/lib/caddy`. Do not change that path without also updating the systemd unit.

## Write the minimal Caddyfile

The Caddyfile lives at `/etc/caddy/Caddyfile`. A working single-site config:

```
example.com {
    reverse_proxy localhost:3000
    encode zstd gzip
    log {
        output file /var/log/caddy/access.log {
            roll_size 10mb
            roll_keep 5
        }
    }
}
```

That is the complete config for HTTPS termination with compression and log rotation. Caddy handles ACME challenges, cert storage, and renewal automatically. Reload after changes:

```bash
caddy reload --config /etc/caddy/Caddyfile
# or
systemctl reload caddy
```

Test for syntax errors before reloading:

```bash
caddy validate --config /etc/caddy/Caddyfile
```

## Serve multiple sites from one Caddyfile

Add additional site blocks for each domain. Caddy provisions a separate cert for each:

```
api.example.com {
    reverse_proxy localhost:4000
    encode zstd gzip
}

static.example.com {
    root * /srv/static
    file_server
    encode zstd gzip
}

example.com {
    reverse_proxy localhost:3000
}
```

Each block is independent. Caddy renews each cert on its own schedule. A cert failure for one site does not affect others.

## Use headers and redirects for common patterns

Force www to apex:

```
www.example.com {
    redir https://example.com{uri} permanent
}
```

Add security headers:

```
example.com {
    reverse_proxy localhost:3000
    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
        X-Frame-Options "DENY"
        X-Content-Type-Options "nosniff"
        Referrer-Policy "strict-origin-when-cross-origin"
        -Server
    }
}
```

The `-Server` directive removes the `Server: Caddy` header. Strip response headers you do not want to expose with `-<HeaderName>`.

## Configure Caddy behind Cloudflare in Full Strict mode

When [[ops/cloudflare]] proxies traffic to the VPS in Full Strict mode, Cloudflare validates the origin's TLS certificate. Caddy obtains a valid Let's Encrypt cert on port 443, which satisfies Full Strict.

The ACME HTTP-01 challenge requires that Cloudflare pass port-80 traffic to the VPS for cert provisioning. Ensure Cloudflare's SSL/TLS mode is Full Strict (not Flexible), and that the VPS firewall allows port 80 from Cloudflare's IP ranges. Alternatively, use the Caddy `tls` block with the Cloudflare DNS plugin to issue certs via DNS-01 challenge without exposing port 80.

Trust real client IPs by restoring them from Cloudflare's `CF-Connecting-IP` header:

```
example.com {
    reverse_proxy localhost:3000 {
        header_up X-Real-IP {http.request.header.CF-Connecting-IP}
    }
}
```

Without this, your app sees Cloudflare edge IPs for every request, which breaks IP-based rate limiting and geo-detection.

## Manage Caddy logs and debug failures

Check the Caddy service log for ACME errors:

```bash
journalctl -u caddy -f
```

ACME failures usually have one of three causes: port 80 blocked by UFW, a DNS record that does not point to the VPS yet, or a Cloudflare proxied record that intercepts the ACME challenge. Confirm DNS resolution before troubleshooting ACME:

```bash
dig +short example.com
# Should return the VPS IP, not a Cloudflare IP, during initial cert provisioning
```

## Related

- [[ops/hostinger-vps]]
- [[ops/hostinger-vps-hardening]]
- [[ops/hostinger-vps-deploy-systemd]]
- [[ops/hostinger-vps-backups]]
- [[ops/cloudflare]]
- [[comparisons/nginx-vs-caddy]]
