Linux — практическо ръководство
Част 9: Cloudflare Tunnel — 6 сайта без един отворен порт
Дълго време home lab означаваше компромис: или държиш сайтовете си само за вътрешна употреба, или отваряш портове на рутера и се молиш никой сканер да не ги намери. Днес публикувам шест сайта от сървъра си вкъщи и рутерът ми няма нито един пренасочен порт. Инструментът, който го прави възможно, е Cloudflare Tunnel.
ℹ Бележка Това е описанието на моето реално setup: Proxmox VE на физическия сървър, отделен LXC контейнер за Docker, а всички публични услуги минават през един cloudflared конектор. Командите са от Debian-базиран LXC. Не е нужен статичен IP адрес — това е една от най-големите ползи от този подход.
1. Проблемът, който тунелът решава
Класическият начин да публикуваш услуга от домашна мрежа изглежда така: на рутера се отваря порт 443, прави се port forwarding към вътрешния сървър и динамичният IP адрес се обвързва с DDNS услуга. Този модел има три проблема, които лично ме притесняваха:
- Повърхност за атака. Отворен порт означава, че всеки бот в интернет може да го сканира, да опита exploit срещу Nginx, да пипа TLS handshake-а. Дори конфигурацията да е чиста, аз трябва да я поддържам чиста всеки ден.
- Динамичен IP. Доставчикът ми сменя адреса. DDNS решава част от проблема, но добавя още една движеща се част, която може да се развали тихо.
- Доверителност на рутера. Поредния ISP рутер е черна кутия. Не знам какво firmware тича, какви уязвимости има и кога ще получи последния си ъпдейт.
С Cloudflare Tunnel посоката на връзката се обръща: сървърът ми излиза навън и поддържа постоянна изходяща връзка към Cloudflare, а посетителите отиват при Cloudflare, не при мен. Рутерът не получава нито едно входящо правило.
2. Как работи — една картинка вместо десет изречения
cloudflared е малък демон, който тича на сървъра ми. При старта си той установява изходяща
QUIC връзка към два от closest Cloudflare центровете и я поддържа жива. Когато някой отвори
serafiev.com, заявката стига до Cloudflare Edge, през вече установения тунел стига до моя
конектор, а конекторът я предава на правилния локален контейнер.

Три неща си струва да забележите в схемата:
- Домейнът сочи към тунела, не към IP. DNS записът за всеки сайт е CNAME към
<TUNNEL-ID>.cfargotunnel.com. Нямам A запис с домашния адрес, значи адресът ми не се изтича през DNS. - TLS terminates при Edge. Посетителят говори HTTPS с Cloudflare, а тунелът носи криптиран трафик към мен. Сертификати не управлявам изобщо — Let’s Encrypt и cron jobs за подновяване отпадат от уравнението.
- Няма нищо за сканиране. Порт 80 и 443 на рутера са затворени. Ssh-ботовете, които обикалят интернет, виждат… нищо.
3. Какво ми е необходимо
Списъкът е кратък:
- Домейн, управляван в Cloudflare (free планът е достатъчен — тунелът не е платена функция).
- Сървър с Linux — в моя случай LXC контейнер с Docker върху Proxmox VE.
- cloudflared — конекторът, инсталиран на сървъра.

За шестте сайта в моята лаборатория един-единствен конектор е напълно достатъчен — те споделят един cloudflared процес, а разпределянето става по hostname правило в конфигурацията.
4. Инсталирам cloudflared на Debian
Cloudflare поддържа собствен apt repository. Добавям го ръчно:
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
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
sudo apt-get update && sudo apt-get install cloudflaredПроверявам инсталацията:
cloudflared --version⚠ Предупреждение В LXC контейнер без systemd (unprivileged шаблон без systemd enable) не мога да разчитам на
systemctl. Аз тичам cloudflared като Docker контейнер точно заради това — вижте раздел 8. За класическа инсталация на VM или физическа машина обаче deb пакетът с вградената systemd услуга е най-простият път.
5. Свързвам конектора с Cloudflare акаунта
Първо се автентикирам:
cloudflared tunnel loginКомандата дава URL — отварям го, избирам домейна си и Cloudflare сваля сертификатът в
~/.cloudflared/cert.pem. Този файл оторизира тази машина да създава тунели в акаунта.
После създавам самия тунел и получавам уникален идентификатор:
cloudflared tunnel create homelabОтговорът изглежда приблизително така:
Created tunnel homelab with id 1a2b3c4d-5e6f-...Записвам си ID-то — то влиза в DNS CNAME записите и в конфигурацията.
6. Конфигурирам ingress правилата
Сърцето на цялата схема е config.yml. Там описвам кое hostname към кой локален порт се
пренасочва. Моят опростен вариант:
tunnel: 1a2b3c4d-5e6f-...
credentials-file: /etc/cloudflared/1a2b3c4d-5e6f-....json
ingress:
- hostname: serafiev.com
service: http://172.19.0.1:8080
- hostname: dev.fedia.eu
service: http://172.19.0.1:8081
- hostname: status.fedia.eu
service: http://172.19.0.1:8082
- service: http_status:404Три правила, които спазвам:
- Последният ред е задължителен.
http_status:404е catch-all — без него cloudflared отказва да тръгне. Всеки хостнейм, който не съм описал, получава 404. - IP адрес вместо
localhost. Конекторът тича в контейнер, а сайтовете — в други. Затова соча към bridge интерфейса на Docker мрежата, не към 127.0.0.1. - HTTP локално е ок. Трафикът между мен и Cloudflare е криптиран от самия тунел. В
локалната мрежа не ми трябва втори TLS слой — но ако услугата поддържа, може и
https://сoriginRequest: { noTLSVerify: true }.
DNS записите мога да ги направя и от CLI-то, без да влизам в Dashboard:
cloudflared tunnel route dns homelab serafiev.com
cloudflared tunnel route dns homelab dev.fedia.eu
cloudflared tunnel route dns homelab status.fedia.euВсяка команда създава CNAME <hostname> → <tunnel-id>.cfargotunnel.com.
7. Пускам тунела и го проверявам
cloudflared tunnel run homelabГледам логовете: търся редове от вида Registered tunnel connection — това са активните
връзки към Edge. После проверявам отвън:
curl -sI https://serafiev.com | head -3Ако виждам HTTP/2 200 с server: cloudflare, схемата работи. След това остава само да
осигуря автоматичен рестарт при рестарт на сървъра.
✓ Успех Моят навик: след всяка промяна в config.yml правя
cloudflared tunnel ingress validate. Така хващам синтактични грешки и липсващ catch-all, преди да са счупили всички сайтове наведнъж.
8. Моето решение: cloudflared като Docker контейнер
Понеже всичко останало в лабата ми е контейнеризирано, и конекторът е контейнер. Така
ъпгрейдът е docker compose pull, а конфигурацията живее заедно с останалите compose
файлове:
services:
cloudflared:
image: cloudflare/cloudflared:latest
restart: unless-stopped
command: tunnel --no-autoupdate run
environment:
- TUNNEL_TOKEN=${CLOUDFLARED_TOKEN}
network_mode: hostТокенът взимам от Dashboard-а (Zero Trust → Networks → Tunnels → избрания тунел) и го
държам в .env файл до compose файла. При този модел ingress правилата се управляват
централно в Dashboard-а, а не в локален config.yml — двата подхода не се смесват, избира
си човек единия.
network_mode: host е умишлено: така контейнерът вижда локалната мрежа на хоста и стига
до останалите услуги без допълнителни Docker network танци.
9. Какво спечелих — старият начин срещу тунела
| Критерий | Port forwarding + DDNS | Cloudflare Tunnel |
|---|---|---|
| Отворени портове на рутера | 80 и/или 443 | 0 |
| Повърхност за атака | Всеки сканер стига до Nginx | Edge филтрира преди мен |
| TLS сертификати | Собствени, с подновяване | Managed при Edge |
| Динамичен IP | Нужен DDNS | Без значение |
| DDoS защита | Няма | Включена в плана |
| Скриване на реалния IP | Частично | Пълен — няма A запис |
Последният ред често го подценяват: когато няма A запис, никой не може да разбере къде физически е сървърът. За мен, който държи всички инфраструктурни сайтове вкъщи, това не е параноично — това е хигиена.
10. Сигурност на няколко нива
Тунелът решава мрежовия въпрос, но не заменя останалата дисциплина. При мен:
- Файрволът на Proxmox пуска към cloudflared LXC-а само изходящ трафик и локалния мрежови диапазон. Входящи правила от интернет — нулев брой.
- Cloudflare WAF и Bot Fight Mode филтрират смислената част от боклука още при Edge. Два клика в Dashboard-а, безплатно.
- Cloudflare Access за административните интерфейси: Proxmox UI-то и мониторингът не са публични — преди да стигнат до тях, Cloudflare иска SSO автентикация.
- Редовни бекъпи, които тичат автоматично — защото най-добрата защита от инцидент си остава възможността да възстановиш.
11. Проблемите, които срещнах
За да не учите същите уроци трудният начин:
- QUIC е блокиран от някои ISP-и. Ако в логовете се вижда цикъл на reconnect, пробвам
--protocol http2в командата на cloudflared. По-бавен handshake, но стабилен. - Разпространение на DNS. След
route dnsновият hostname може да не отговори минути — не паникьосвам, а проверявам сdig CNAME hostname +short. - Забравен catch-all. След добавяне на нов сайт без
http_status:404накрая тунелът не тръгна и шестте сайта паднаха едновременно. Оттогава validate е ритуал. - Прекалено много доверие в локалната мрежа. Тунелът не пази от повреден контейнер, който слуша на всички интерфейси. Затова файрволът на Proxmox е второто ниво, а не допълнение.
12. Командите накратко
| Команда | За какво я използвам |
|---|---|
cloudflared tunnel login | Оторизирам машината в Cloudflare акаунта. |
cloudflared tunnel create NAME | Създавам тунел и получавам ID. |
cloudflared tunnel route dns NAME HOST | Създавам CNAME запис към тунела. |
cloudflared tunnel ingress validate | Проверявам config.yml преди рестарт. |
cloudflared tunnel run NAME | Стартирам тунела на преден план. |
cloudflared tunnel list | Показвам тунелите в акаунта. |
cloudflared tunnel info NAME | Виждам статус и активни конектори. |
cloudflared tunnel delete NAME | Изтривам тунел. |
systemctl status cloudflared | Проверявам услугата при systemd инсталация. |
journalctl -u cloudflared -f | Следя логовете на живо. |
13. Заключение
Cloudflare Tunnel обърна моята логика на публикуване: вместо да отварям вход и да пазя дверите, сървърът ми излиза навън и работи през връзка, която сам той е установил. Шестте сайта в лабораторията ми живеят зад един конектор, един YAML файл и нулата пренасочени порта на рутера.
Не е сребърен куршум — зависиш от Cloudflare и от това, че конекторът е жив. Но за моя масштаб (домашен сървър, няколко услуги, малко време за поддръжка) балансът между сигурност, цена и простота е непобедим.
✓ Успех Моят принцип: най-безопасният отворен порт е онзи, който никога не е бил отварян. Тунелът просто спестява и последния компромис.
