headscale: איך לארח שרת Tailscale משלך
מדריך להקמת שרת בקרה עצמאי של Tailscale על VPS: מתקינים headscale מחבילת .deb הרשמית, מגדירים server_url לפני ההפעלה ומחברים את הצומת הראשון.
מהו headscale
headscale הוא מימוש באירוח עצמי של שרת הבקרה של Tailscale. לכן, המכונה שמתאמת את הרשת הפרטית שלך היא VPS שבבעלותך. זהו פרויקט קהילתי, והוא אינו מופעל על ידי Tailscale Inc. בכל מכונה עדיין פועל הלקוח הרשמי tailscale, כשהוא מוגדר להתחבר לשרת שלך באמצעות דגל אחד: --login-server.
שרת הבקרה הוא הרכיב שיודע מי שייך לרשת. הוא מקצה לכל צומת כתובת מתוך 100.64.0.0/10, מפיץ מפתחות ציבוריים ומודיע לצמתים היכן למצוא זה את זה. המנהרות נשארות מנהרות WireGuard, שנבנות מצומת לצומת. התעבורה בין שתי מכונות שלך אינה עוברת דרך שרת headscale, אלא אם אי-אפשר ליצור נתיב ישיר והצמתים עוברים להשתמש בממסר.
headscale משרת tailnet אחד (רשת Tailscale אחת) בכל מופע. הפרויקט מתאר תצורה זו כמתאימה לשימוש אישי או לארגון קטן. אם יש לך שלוש או ארבע מכונות, VPN פשוט של WireGuard ב-VPS שבבעלותך דורש פחות תוכנות לתפעול ופחות רכיבים שעלולים להתקלקל. headscale משתלם כאשר אינך רוצה עוד לכתוב בלוק [Peer] באופן ידני עבור כל מחשב נייד חדש. להשוואה רחבה יותר בין שני המודלים, ראו כיצד WireGuard ו-Tailscale שונים זה מזה.
מה נדרש לפני ההתקנה
- VPS המריץ Ubuntu 24.04, עם כתובת IPv4 ציבורית וגישת
sudo. אם השרת חדש, השלימו תחילה את עשר הדקות הראשונות ב-VPS חדש. - רשומת A ב-DNS המצביעה לכתובת זו. במדריך זה נעשה שימוש ב-
headscale.example.com. - דומיין או תת־דומיין נוסף עבור MagicDNS. במדריך זה נעשה שימוש ב-
tailnet.example.net. הוא לא יכול להיות אותו דומיין שמופיע ב-server_url. - מחשב לקוח אחד להצטרפות, המריץ Linux, macOS, Windows, Android או iOS.
התקנת headscale מהחבילה הרשמית בפורמט .deb
הפרויקט מפרסם חבילות .deb בדף המהדורות שלו ב-GitHub. נכון ליולי 2026, המהדורה הנוכחית היא 0.29.3. בדקו תחילה את הארכיטקטורה, מכיוון ששם הקובץ כולל אותה.
sudo apt update
sudo apt install -y wget
dpkg --print-architectureהפקודה מדפיסה amd64 ב-VPS רגיל המבוסס על x86, ו-arm64 בתוכנית בסגנון Ampere או Graviton. הציבו את התשובה במשתנה שלהלן.
HEADSCALE_VERSION="0.29.3"
HEADSCALE_ARCH="amd64"
wget --output-document=headscale.deb \\
"https://github.com/juanfont/headscale/releases/download/v${HEADSCALE_VERSION}/headscale_${HEADSCALE_VERSION}_linux_${HEADSCALE_ARCH}.deb"
sudo apt install -y ./headscale.deb
headscale versionה-./ שלפני שם הקובץ נדרש. בלעדיו, apt מחפש במאגרים שלכם חבילה בשם headscale.deb ונכשל.
החבילה יוצרת משתמש מערכת בשם headscale, כותבת קובץ תצורה ברירת מחדל בשם /etc/headscale/config.yaml ומתקינה יחידת systemd. היא אינה מפעילה את השירות, וזהו הסדר הנכון. התצורה שסופקה מפנה את server_url אל http://127.0.0.1:8080, שאינה כתובת שאליה לקוח כלשהו שלכם יכול להתחבר. לכן, הפעלת השירות כעת תהיה שגויה גם אם הוא יעלה. הפעלת sudo systemctl is-active headscale בשלב זה מדפיסה inactive. זו התנהגות צפויה, ולא תקלה.
הגדרת server_url לפני הפעלת השירות
ערוך את /etc/headscale/config.yaml באמצעות sudo nano /etc/headscale/config.yaml, או החל את אותם שלושה שינויים באמצעות sed. שמור עותק של הקובץ המקורי, מכיוון שהוא ארוך וכולל הערות רבות, והוא משמש כאסמכתה הטובה ביותר להמשך הגדרת האפשרויות.
sudo cp /etc/headscale/config.yaml /etc/headscale/config.yaml.orig
sudo sed -i 's|^server_url:.*|server_url: https://headscale.example.com|' /etc/headscale/config.yaml
sudo sed -i 's|^listen_addr:.*|listen_addr: 127.0.0.1:8080|' /etc/headscale/config.yaml
sudo sed -i 's|^ base_domain:.*| base_domain: tailnet.example.net|' /etc/headscale/config.yaml
sudo grep -E '^(server_url|listen_addr):|^ base_domain:' /etc/headscale/config.yamlserver_url היא הכתובת ש- headscale כותב בכל רישום של לקוח. לאחר מכן הלקוחות מתחברים תמיד למחרוזת המדויקת הזו, ולכן היא חייבת להיות השם הציבורי עם https:// לפניו, ולעולם לא 127.0.0.1.
listen_addr היא הכתובת שאליה התהליך נקשר. השאר אותה על loopback. reverse proxy באותו שרת מסיים את TLS (אבטחת שכבת התעבורה) ומעביר אליו את הבקשות, ולכן אין צורך שגורם מחוץ לשרת יוכל להגיע אל port 8080.
base_domain היא הסיומת של MagicDNS, כלומר הדומיין שמתחתיו הצמתים מקבלים שמות. היא חייבת להיות fully qualified domain name ללא נקודה בסוף, והיא חייבת להיות דומיין שונה מזה שב-server_url, משום שאחרת שני מרחבי השמות יתנגשו.
אל תשנה את מקטע מסד הנתונים. ברירת המחדל היא SQLite ב-/var/lib/headscale/db.sqlite, בתוך ספרייה שהחבילה יצרה ובבעלותה, ו-SQLite מספיק עבור tailnet בגודל כזה.
הפעלת headscale והוכחה שהוא פועל
sudo systemctl enable --now headscale
sudo systemctl is-active headscale
curl -sS -o /dev/null -w '%{http_code}\\n' http://127.0.0.1:8080/healthis-active מדפיס את active, ו-curl מדפיס את 200. enable --now מבצע את שתי הפעולות: הוא מפעיל את השירות ומגדיר אותו להפעלה לאחר אתחול מחדש.
אם is-active מדפיס את failed, קראו את היומן באמצעות sudo journalctl -u headscale -n 50 --no-pager. כשל בשלב זה נובע כמעט תמיד מקובץ התצורה, מכיוון ש-headscale מנתח את הקובץ כולו לפני שהוא פותח socket. לכן הזחה שגויה או מפתח לא מוכר עוצרים את התהליך לפני שהשירות מתחיל להאזין. תקנו את הקובץ ולאחר מכן הפעילו sudo systemctl restart headscale. כל שינוי תצורה מאוחר יותר דורש אתחול זהה. הלקוחות מתחברים מחדש באופן עצמאי לאחר מכן. אם יחידות systemd חדשות לכם, הפעלת שירותים ו-timers משלכם באמצעות systemd מסבירה את הפקודות המשמשות כאן.
בדקו את קובצי המצב בזמן שאתם נמצאים ב-shell:
stat -c '%U %n' /var/lib/headscale/db.sqlite /var/lib/headscale/noise_private.keyשתי השורות מתחילות ב-headscale, המשתמש ללא הרשאות שהחבילה יצרה. noise_private.key הוא הזהות של השרת מול הלקוחות שלו. השאירו אותו. אם תמחקו אותו, headscale ייצור זהות חדשה, וכל node יצטרך להירשם מחדש.
הצבת TLS לפני headscale
הלקוחות חייבים לגשת אל server_url באמצעות HTTPS. Caddy הוא הנתיב הקצר ביותר, מכיוון שהוא מבקש את האישור ומחדש אותו באופן עצמאי.
sudo apt install -y caddyהחליפו את /etc/caddy/Caddyfile בבלוק מתוך התיעוד של headscale:
headscale.example.com {
reverse_proxy 127.0.0.1:8080 {
header_up True-Client-IP {remote_host}
header_up X-Real-IP {remote_host}
}
}sudo caddy validate --adapter caddyfile --config /etc/caddy/Caddyfile
sudo systemctl restart caddy
sudo systemctl is-active caddyהפקודה validate מדפיסה את adapted config to JSON כאשר הקובץ נותח בהצלחה. אזהרה שלפיה הקובץ אינו מעוצב היא קוסמטית. מהמחשב הנייד שלכם, גם curl -sS -o /dev/null -w '%{http_code}\\n' https://headscale.example.com/health אמורה להדפיס את 200. בדיקה יחידה זו מוכיחה ש-DNS, חומת האש, האישור וה-Proxy פועלים יחד.
זהו פרט בתצורת ה-Proxy שגורם לעיתים לבזבוז של ערב שלם. חיבור הבקרה של Tailscale הוא שדרוג HTTP. הוא מתחיל באמצעות POST ולא באמצעות GET, והערך של הכותרת Upgrade הוא tailscale-control-protocol. Caddy מעביר זאת ללא תצורה נוספת. nginx אינו עושה זאת, ולכן חזית nginx זקוקה למפת השדרוג:
map $http_upgrade $connection_upgrade {
default keep-alive;
'' close;
}
server {
listen 443 ssl;
server_name headscale.example.com;
location / {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_buffering off;
proxy_pass http://127.0.0.1:8080;
}
}אם תשמיטו את השורות האלה, בקשות רגילות עדיין יצליחו. לכן /health מחזירה 200 והכול נראה תקין, בעוד שחיבור הבקרה המתמשך אינו נוצר והצמתים שלכם נרשמים ולאחר מכן נשארים במצב לא מקוון. אם בחרתם ב-nginx, המדריך Certbot ב-Ubuntu 24.04 עם nginx מכסה את שלב האישור.
אילו יציאות לפתוח ב-UFW
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verboseיציאה 443 נושאת את כל התקשורת עם הלקוחות. יציאה 80 משמשת רק לאתגר HTTP של ACME (סביבת ניהול אישורים אוטומטית) ולהפניה ל-HTTPS. Caddy זקוק לה כדי לקבל אישור.
יציאה 8080 נשארת סגורה. listen_addr הוא 127.0.0.1:8080, ולכן ה-proxy ניגש אל headscale דרך ממשק ה-loopback, ואין צורך בכלל של חומת אש. פתיחת 8080 לאינטרנט מספקת ללקוחות ערוץ בקרה גלוי ומוצפן-לא, ואינה מועילה. חשוב לזכור שרוב הספקים מפעילים חומת אש נוספת בלוח הבקרה שלהם, בנפרד מ-UFW. לכן יציאה יכולה להיות פתוחה במחשב ועדיין סגורה בקצה הרשת. יסודות חומת האש UFW ב-VPS מסביר ביתר פירוט את תחביר הכללים.
יצירת משתמש ומפתח preauth
sudo headscale users create alice
sudo headscale users listהפקודה headscale היא לקוח. היא מתקשרת עם ה-daemon שפועל דרך ה-unix socket בנתיב /var/run/headscale/headscale.sock, שההרשאות שלו הן 0770 והבעלות עליו היא של הקבוצה headscale. מכאן נובעות שתי מסקנות. הפקודה נכשלת כשהשירות מופסק, וזו סיבה נוספת לכך שסדר הפעולות במדריך הזה חשוב. בנוסף, נדרשת הרשאת sudo, אלא אם תוסיפו את החשבון שלכם לקבוצה headscale.
users list מדפיסה מזהה לצד כל שם. אתם זקוקים למספר הזה, משום שהפקודה ליצירת המפתח מקבלת מזהה משתמש מספרי ולא שם.
sudo headscale preauthkeys create --user 1 --expiration 24hהמפתח מודפס פעם אחת בלבד. העתיקו אותו עכשיו. מפתח preauth מיועד לשימוש יחיד ותקף למשך שעה אחת, אלא אם תציינו אחרת. לכן כדאי להגדיר את --expiration 24h בזמן שאתם עדיין בודקים את המערכת. הוסיפו את --reusable כדי ליצור מפתח שירשום כמה מכונות, והתייחסו אליו כמו לסיסמה, משום שכל מי שמחזיק בו יכול להצטרף לרשת שלכם.
חיבור הלקוח הראשון באמצעות --login-server
במחשב שברצונכם לצרף:
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up --login-server https://headscale.example.com --auth-key 'hskey-auth-PASTE-YOUR-KEY-HERE'
tailscale status
tailscale ip -4tailscale ip -4 מדפיס את הכתובת ש- headscale הקצה, למשל 100.64.0.1. בחזרה בשרת, sudo headscale nodes list מציג את הצומת עם המזהה שלו, המשתמש שלו ומצב החיבור שלו.
הערך של --login-server חייב להתאים בדיוק ל- server_url, כולל הסכמה וללא לוכסן בסוף. הערכים מושווים כמחרוזות. אי-התאמה גורמת לכך שהלקוח נרשם מול כתובת אחת, ולאחר מכן מקבל הוראה לתקשר עם כתובת אחרת.
מחשב שהיה מחובר בעבר לשירות המתארח של Tailscale שומר את החיבור הזה. הפעילו בו תחילה את sudo tailscale logout, ולאחר מכן את tailscale up עם --login-server.
אם תשמיטו את --auth-key, הלקוח ידפיס כתובת URL במקום זאת. פתחו אותה. בדף יוצג המזהה של ניסיון הרישום, שאותו תאשרו בשרת:
sudo headscale auth register --user alice --auth-id PASTE-THE-ID-FROM-THE-PAGEטופס זה נוח יותר עבור המחשב הנייד האישי שלכם. מפתחות רישום מראש מתאימים יותר לכל פעולה שמבוצעת באמצעות סקריפט, משום שאין צורך באדם שיפקח עליה.
DERP, ומה ממסר תעבורת רשת כאשר נתיב ישיר נכשל
DERP (ממסר מוצפן ייעודי למנות) הוא נתיב חלופי. כאשר שני צמתים אינם יכולים לפתוח חיבור ישיר של WireGuard, בדרך כלל מפני ששניהם נמצאים מאחורי NAT (תרגום כתובות רשת) מחמיר, הם שולחים את המנות דרך ממסר. הממסר אינו מחזיק מפתחות, ולכן אינו יכול לקרוא את תעבורת הרשת שלכם. עם זאת, הוא יכול לראות אילו צמתים מתקשרים וכמה נתונים מועברים.
חשוב להבין מה עושה תצורת ברירת המחדל. Headscale מופץ כשהוא מצביע על https://controlplane.tailscale.com/derpmap/default באמצעות auto_update_enabled: true ו-update_frequency: 3h, כך שמישור הבקרה נמצא בשליטתכם, ואילו הממסרים שייכים ל-Tailscale. עבור רוב המשתמשים זהו איזון סביר. אם לא, הפעילו ממסר משלכם.
כדי להפעיל ממסר משלכם, הגדירו את enabled: true תחת derp.server בקובץ config.yaml, הפעילו מחדש את headscale ופתחו את יציאת STUN (כלי מעבר הפעלה עבור NAT) באמצעות sudo ufw allow 3478/udp. קובץ התצורה מציין את הדרישה במפורש: server_url חייב להשתמש ב-https, מפני ש-DERP דורש TLS. ריקון הרשימה derp.urls מסיר את הממסרים של Tailscale מהמפה. אם תעשו זאת ללא ממסר משובץ פעיל, כל זוג צמתים שאינו יכול להתחבר ישירות לא יוכל להתחבר כלל.
מלקוח, tailscale netcheck מציג את זמן ההשהיה לכל אזור ממסר המוכר לו, ו-tailscale status מסמן כל עמית כ-direct עם כתובת או כ-relay עם קוד אזור. עמית שנשאר במצב relay מצביע על בעיית NAT, ולא על בעיה ב-headscale.
מדוע צומת מופיע במצב לא מקוון?
הפרוקסי משמיט את בקשת השדרוג. זהו המקרה הנפוץ, והסימן שלו הוא שכל השאר נראה תקין: /health מחזיר 200, headscale nodes list מציג את הצומת, והצומת אינו עובר למצב מקוון. חיבור הבקרה הוא בקשת POST המכילה את Upgrade: tailscale-control-protocol, ופרוקסי שאינו מעביר אותה משבית את הערוץ היחיד שמדווח על מצב הצומת. השוו את תצורת nginx שלכם לבלוק המיפוי שלמעלה, או עברו ל-Caddy כדי לשלול את הפרוקסי כגורם.
server_url השתנה לאחר שהצמתים נרשמו. הצמתים ממשיכים להתחבר לערך שנמסר להם בעת הרישום. אם ערכתם אותו, הריצו sudo tailscale up --login-server https://headscale.example.com --force-reauth בכל צומת.
הלקוח אינו פועל. בצומת, הריצו sudo systemctl is-active tailscaled ו-sudo journalctl -u tailscaled -n 50 --no-pager. לקוח שאינו מצליח לפתור את שם התחום שלכם או להגיע אליו רושם שם את ניסיונות החיבור החוזרים שלו.
פג תוקף המפתח. הנושא מוסבר בסעיף הבא.
כדי לנטר את צד השרת במהלך הבדיקה, הריצו sudo journalctl -u headscale -f ב-VPS והפעילו מחדש את tailscaled בלקוח. צומת שמגיע אל headscale יוצר מיד שורות ביומן. היעדר פלט פירושו שהבקשה אינה מגיעה, לכן בדקו את DNS, את חומת האש ואת הפרוקסי לפני שתבדקו את headscale.
פקיעת מפתחות והצומת שמפסיק לפעול כעבור כמה שבועות
קיימות שתי פקיעות נפרדות, ובלבול ביניהן מבזבז זמן.
מפתחות Preauth פוקעים במהירות, בכוונה תחילה. ברירת המחדל היא שעה אחת ושימוש אחד. אם tailscale up דוחה את המפתח, צרו מפתח חדש בשרת במקום לערוך משהו אצל הלקוח.
מפתחות צמתים הם הרכיב ארוך-הטווח. הקטע node של config.yaml מגדיר את expiry: 0, ו-0 פירושו שאין פקיעה כברירת מחדל: צומת רשום נשאר בתוקף עד שתפקיעו אותו. צמתים מתויגים אינם פוקעים לעולם. הגדירו את expiry: 180d אם ברצונכם לגרום לרישומים לפקוע עם הזמן, והבינו את המשמעות: כל צומת שאינו מתויג יזדקק לאחר מכן ל-sudo tailscale up --login-server https://headscale.example.com --force-reauth לפי לוח זמנים זה, ושרת ללא ממשק משתמש, שאף אחד אינו מבצע בו אימות מחדש, ינותק מהרשת מעצמו.
בצעו זאת ידנית כאשר מישהו מאבד מחשב נייד. sudo headscale nodes list מציג את המזהה, לאחר מכן sudo headscale nodes expire -i 3 מנתק את הצומת, ו-sudo headscale nodes delete -i 3 מסיר אותו לחלוטין מהרשת.
גיבויים ושדרוגים
/var/lib/headscale ו-/etc/headscale יחד מהווים את כל השרת. עצרו את השירות לפני העתקתם, מכיוון ש-SQLite עשוי לבצע כתיבות שטרם הושלמו, ובסיס נתונים שהועתק בזמן עומס עלול להיות לא עקבי.
sudo systemctl stop headscale
sudo tar czf /root/headscale-state.tgz -C /var/lib headscale
sudo tar czf /root/headscale-config.tgz -C /etc headscale
sudo systemctl start headscale
sudo chmod 600 /root/headscale-*.tgzהעבירו את שני הקבצים אל מחוץ למחשב. הם מכילים את המפתחות הפרטיים ואת כל הרישומים, ולכן יש להגן עליהם באותה רמת זהירות כמו על השרת עצמו. גיבויי restic מ-VPS מסביר כיצד לבצע זאת לפי לוח זמנים ובאופן מוצפן.
שדרוגים חוזרים על תהליך ההתקנה: הורידו את .deb החדש ואת sudo apt install ./headscale.deb, לאחר מכן הפעילו מחדש את השירות והריצו שוב את בדיקות is-active ו-/health. החל מגרסה 0.29, נתיב השדרוג מחמיר. דילוג על גרסה משנית חסום, וכך גם חזרה לגרסה משנית ישנה יותר. עברו גרסה משנית אחת בכל פעם, בצעו גיבוי לפני כל שלב, וקראו תחילה את הערות הגרסה של אותה גרסה, מכיוון שאותה גרסה שינתה את התנהגות מדיניות ה-ACL והעבירה כמה ממפתחות התצורה.
FAQ
מדוע headscale אינו מצליח להפעיל מיד לאחר התקנת קובץ ה־.deb?
החבילה מתקינה את היחידה, אך משאירה את השירות במצב מופסק. ברירת המחדל /etc/headscale/config.yaml היא תבנית ולא תצורה פעילה. ערכו תחילה את server_url, את listen_addr ואת base_domain, לאחר מכן הריצו את sudo systemctl enable --now headscale ואמתו באמצעות sudo systemctl is-active headscale. אם השירות עדיין נכשל, sudo journalctl -u headscale -n 50 --no-pager מציין את הבעיה. בשלב זה מדובר כמעט תמיד בשגיאת YAML, משום ש־headscale מנתח את הקובץ כולו לפני שהוא מאזין ביציאה.
האם עדיין עליי להתקין את לקוח Tailscale הרגיל במחשבים שלי?
כן. Headscale מחליף רק את שרת הבקרה. בכל צומת פועל הלקוח הרשמי של Tailscale, ומפנים אותו לשרת באמצעות sudo tailscale up --login-server https://headscale.example.com. הדגל הזה קיים בלקוח הרגיל, ולכן אין צורך בתיקון או בבנייה מחדש.
האם התעבורה שלי עוברת דרך שרת headscale?
בדרך כלל לא. Headscale מתאם את הרשת ומקצה מפתחות וכתובות, בעוד שמישור הנתונים משתמש ב־WireGuard ישירות בין הצמתים. התעבורה עוקפת את הנתיב הישיר רק כאשר שני צמתים אינם יכולים להגיע זה לזה ישירות ונאלצים להשתמש בממסר DERP. בתצורת ברירת המחדל, הממסרים האלה הם הממסרים הציבוריים של Tailscale. הריצו בצומת את tailscale status כדי לבדוק אם עמית מסוים הוא direct או נמצא ב־relay.
מדוע הצומת שלי נשאר במצב לא מקוון לאחר הרישום?
צומת שמופיע ב־headscale nodes list אך לעולם אינו עובר למצב מקוון איבד בדרך כלל את חיבור הבקרה שלו בשרת ה־reverse proxy. החיבור הזה הוא שדרוג HTTP שנשלח באמצעות POST עם הכותרת Upgrade: tailscale-control-protocol, ו־nginx משמיט אותו אלא אם מוסיפים את בלוק map $http_upgrade $connection_upgrade ואת שורות proxy_set_header התואמות. Caddy מעביר את החיבור ללא תצורה נוספת, ולכן הוא דרך מהירה לבדוק אם ה־proxy הוא מקור הבעיה.
האם אני זקוק לשם תחום ול־TLS עבור headscale?
בפועל, כן. הלקוחות מתחברים לכל מחרוזת שמציבים ב־server_url, אישורים מונפקים עבור שמות ולא עבור כתובות IP בלבד, וקובץ התצורה מציין ש־DERP דורש TLS. שם תחום יחד עם Caddy דורש כחמש דקות ומספק נקודת קצה של HTTPS שמתחדשת אוטומטית. הפעלת שרת הבקרה באמצעות HTTP רגיל גורמת לכך שכל תקשורת של לקוח איתו עוברת באינטרנט ללא הצפנה.