# Setting Up Harbor with Let's Encrypt and Cloudflare Tunnel on Ubuntu

Harbor is an enterprise-class private container registry with advanced features like RBAC, replication, vulnerability scanning, and more. In this guide, we'll set up **Harbor** on a **Ubuntu machine**, secure it with **Let's Encrypt certificates**, and use **Cloudflare Tunnel** for the HTTP-01 challenge.

---

## 1\. System Prerequisites

Make sure you meet the following requirements:

* Ubuntu 24.04
    
* Publicly resolvable domain (e.g., [`harbor.kcnaiamh.com`](http://harbor.kcnaiamh.com))
    
* Access to your Cloudflare account
    
* Root privileges or sudo access
    
* Docker engine version 27.5.1
    
* Docker compose version 1.29.2
    

### Install Required Tools

```bash
sudo apt update && sudo apt install -y curl git unzip apt-transport-https ca-certificates gnupg lsb-release jq docker.io docker-compose openssl tar certbot
```

---

## 2\. Install Harbor via Online Installer

### Download Harbor Installer

```bash
cd ~
curl -s https://api.github.com/repos/goharbor/harbor/releases/latest | grep browser_download_url | cut -d '"' -f 4 | grep '\.tgz$' | tail -1 | wget -i -
tar xf harbor-online-installer-v*.tgz
cd harbor
```

### Prepare the `harbor.yml` Configuration

```bash
cp harbor.yml.tmpl harbor.yml
```

Edit the following fields in `harbor.yml`:

```yaml
hostname: harbor.kcnaiamh.com
https:
  port: 443
  certificate: /etc/letsencrypt/live/harbor.kcnaiamh.com/fullchain.pem
  private_key: /etc/letsencrypt/live/harbor.kcnaiamh.com/privkey.pem
```

> You need to specify your own subdomain here.

You can run the following command to change the required filed without opening the file. Also, don't forget to change [`harbor.kcnaiamh.com`](http://harbor.kcnaiamh.com) to your own subdomain.

```sh
sed -i 's/^hostname: .*/hostname: harbor.kcnaiamh.com/g' harbor.yml
sed -i 's/^http:/# http:/g' harbor.yml
sed -i 's/^  port: 80/  # port: 80/g' harbor.yml
sed -i 's|^  certificate: .*|  certificate: /etc/letsencrypt/live/harbor.kcnaiamh.com/fullchain.pem|g' harbor.yml
sed -i 's|^  private_key: .*|  private_key: /etc/letsencrypt/live/harbor.kcnaiamh.com/privkey.pem|g' harbor.yml
```

> ⚠️ Don’t run [`install.sh`](http://install.sh) yet — we need the certs first.

---

## 3\. Setup Cloudflare Tunnel (For Let's Encrypt HTTP-01 Challenge)

> If you have dedicated public IP address from your ISP then you don't need this step.

### Create a Cloudflare Tunnel

1. Install `cloudflared`:
    
    If you don’t have cloudflared installed on your machine, run the following:
    

```bash
# Add cloudflare gpg key
sudo mkdir -p --mode=0755 /usr/share/keyrings
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null

# Add this repo to your apt repositories
echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared any main' | sudo tee /etc/apt/sources.list.d/cloudflared.list

# install cloudflared
sudo apt-get update && sudo apt-get install cloudflared
```

2. Setup `cloudflared` service:
    
    After you have installed cloudflared on your machine, you can install a service to automatically run your tunnel whenever your machine starts:
    

```bash
sudo cloudflared service install eyJhIjoiNGM1MTIxNmFhNGU2MTU0NWM4OGUxMjExMDk3YTNjNWYiLCJ0IjoiODIyZjU0ZjYtMmRlNi00NjNiLWEzZDYtYzdkOWUxMDAxZTkzIiwicyI6Ik16aGhOMlpsWmpNdE5UUXpNeTAwWW1ZMUxXSmxPRE10TW1Rd1lqVTRZamxsWVdSaSJ9
```

> You will get the token when doing the tunnel setup.

3. Now setup appropriate subdomain and domain in the cloudflare route tunnel.
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1753504443296/87bd16ae-c0b3-4529-821e-87b6b80a156d.png align="center")

---

## 4\. Get Let's Encrypt Certificate via Certbot

Since HTTP is tunneled through Cloudflare, Certbot's default HTTP challenge will work.

### Temporarily Serve HTTP for Challenge

```bash
sudo certbot certonly --standalone --preferred-challenges http -d "harbor.kcnaiamh.com" --agree-tos -n -m "naim@gmail.com" --keep-until-expiring
```

> Change subdomain and email address according to yours.

Certs will be saved under:

```bash
/etc/letsencrypt/live/harbor.kcnaiamh.com/
```

---

## 5\. Start Harbor

### 🔧 Install Harbor

```bash
sudo ./install.sh --with-trivy
```

Harbor should now be accessible via:

```bash
https://harbor.kcnaiamh.com
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1753504469079/8bf852f9-b160-4712-b090-9dfe0112446c.png align="center")

Default credentials:

* **Username:** `admin`
    
* **Password:** set in `harbor.yml` (Default pass: `Harbor12345`)
    

---

## Optional: Setup Cert Renewal with Systemd Timer

Create `/etc/systemd/system/renew-harbor-cert.service`:

```ini
[Unit]
Description=Renew Let's Encrypt certs and restart Harbor
[Service]
Type=oneshot
ExecStart=/usr/bin/certbot renew --quiet
ExecStartPost=/opt/harbor/prepare
ExecStartPost=/opt/harbor/install.sh
```

Create the timer `/etc/systemd/system/renew-harbor-cert.timer`:

```ini
[Unit]
Description=Timer for cert renewal

[Timer]
OnCalendar=weekly
Persistent=true

[Install]
WantedBy=timers.target
```

Enable it:

```bash
sudo systemctl daemon-reexec
sudo systemctl enable --now renew-harbor-cert.timer
```

## Clean Up

To delete the certificate, run the following command:

```sh
sudo certbot delete --cert-name harbor.kcnaiamh.com
```

---

## Final Thoughts

Using **Cloudflare Tunnel** with **Let's Encrypt** offers a neat workaround for systems behind NAT or without public IPs. Combined with **Harbor**, this makes a secure and professional-grade private registry feasible on even home labs.

If you're using Harbor in production, consider:

* **Enabling RBAC & LDAP**
    
* **Enabling replication for DR**
