📁 文件部署位置
将生成的配置文件复制到服务器对应位置:
# 创建必要目录
mkdir -p /opt/headscale/{config,headplane,data/storage,headplane/data/storage}
# 复制配置文件
cp headplane-config.yaml /opt/headscale/plane/config.yaml
cp headscale-config.yaml /opt/headscale/config.yaml
cp dns_records.json /opt/headscale/dns_records.json
cp compose.yaml /opt/headscale/⚙️ 配置修改清单
1. Headscale 配置 (/opt/headscale/config.yaml)
# Headscale Configuration File Reference
# Save this as: /opt/headscale/config.yaml
# Full example: https://github.com/juanfont/headscale/blob/main/config-example.yaml
# Server URL that clients will connect to
# MUST be accessible from all clients and use HTTPS in production
server_url: https://headscale.example.com
# Address to listen on
listen_addr: 0.0.0.0:8080
# Metrics endpoint (Prometheus)
metrics_listen_addr: 0.0.0.0:9090
# gRPC endpoint for CLI (local only recommended)
grpc_listen_addr: 127.0.0.1:50443
grpc_allow_insecure: false
# Noise protocol private key
noise:
private_key_path: /var/lib/headscale/noise_private.key
# IP prefix allocation
prefixes:
v4: 100.64.0.0/10
v6: fd7a:115c:a1e0::/48
allocation: sequential
# DERP (relay) configuration
derp:
server:
# Enable embedded DERP server
enabled: true
# Region configuration
region_id: 999
region_code: "headscale"
region_name: "Headscale Embedded DERP"
# Only allow clients from this server
verify_clients: true
# STUN listener for NAT traversal (REQUIRED for DERP)
stun_listen_addr: "0.0.0.0:3478"
# Private key for DERP encryption
private_key_path: /var/lib/headscale/derp_server_private.key
# Auto-add to DERP map
automatically_add_embedded_derp_region: true
# Server public IPs (IMPORTANT: set your actual IPs)
ipv4: 198.51.100.1 # CHANGE THIS to your server's IPv4
ipv6: 2001:db8::1 # CHANGE THIS or remove if no IPv6
# External DERP servers (Tailscale's official servers)
urls:
# Uncomment to use Tailscale's DERP servers as backup
# - https://controlplane.tailscale.com/derpmap/default
# Custom DERP map files
paths: []
# Auto-update DERP map
auto_update_enabled: true
update_frequency: 3h
# Disable update checks
disable_check_updates: false
# Ephemeral node timeout
ephemeral_node_inactivity_timeout: 30m
# Database configuration
database:
type: sqlite
debug: false
gorm:
prepare_stmt: true
parameterized_queries: true
skip_err_record_not_found: true
slow_threshold: 1000
sqlite:
path: /var/lib/headscale/db.sqlite
write_ahead_log: true
wal_autocheckpoint: 1000
# TLS/ACME configuration (if not using reverse proxy)
acme_url: https://acme-v02.api.letsencrypt.org/directory
acme_email: ""
tls_letsencrypt_hostname: ""
tls_letsencrypt_cache_dir: /var/lib/headscale/cache
tls_letsencrypt_challenge_type: HTTP-01
tls_letsencrypt_listen: ":http"
# Custom TLS certificates
tls_cert_path: ""
tls_key_path: ""
# Logging
log:
level: info
format: text
# ACL Policy
policy:
# Mode: "file" or "database" (database recommended for Headplane)
mode: database
# If using file mode, specify path
# path: ""
# DNS Configuration
dns:
# MagicDNS base domain (must be different from server_url domain)
base_domain: example.com
# Override local DNS settings
override_local_dns: true
# Nameservers
nameservers:
global:
- 1.1.1.1
- 1.0.0.1
- 2606:4700:4700::1111
- 2606:4700:4700::1001
# Split DNS (optional)
split: {}
# Search domains
search_domains: []
# Extra DNS records (inline)
extra_records: []
# Extra DNS records from file (RECOMMENDED for Headplane)
extra_records_path: /var/lib/headscale/dns_records.json
# Unix socket for CLI
unix_socket: /var/run/headscale/headscale.sock
unix_socket_permission: "0770"
# OpenID Connect (OIDC) - OPTIONAL
# oidc:
# only_start_if_oidc_is_available: true
# issuer: "https://your-oidc.issuer.com/path"
# client_id: "your-oidc-client-id"
# client_secret: "your-oidc-client-secret"
# # Or use file:
# # client_secret_path: "${CREDENTIALS_DIRECTORY}/oidc_client_secret"
# expiry: 180d
# use_expiry_from_token: false
# scope: ["openid", "profile", "email"]
# extra_params: {}
# allowed_domains: []
# allowed_groups: []
# allowed_users: []
# strip_email_domain: true
# map_legacy_users: true
# Logtail (Tailscale telemetry) - disabled by default
logtail:
enabled: false
# Randomize WireGuard client port (firewall workaround)
randomize_client_port: false必须修改的项:
server_url: 改为你的域名,如https://hc.example.comderp.server.ipv4: 改为你服务器的公网 IPv4 地址derp.server.ipv6: 如无 IPv6 则删除此行dns.base_domain: 改为你的内网域名,如tailnet.local
建议修改的项:
derp.server.region_code: 自定义区域代码derp.server.region_name: 自定义区域名称
2. Headplane 配置 (/opt/headscale/plane/config.yaml)
# Headplane Configuration File
# Save this as: /opt/headscale/plane/config.yaml
# Server configuration
server:
host: "0.0.0.0"
port: 3000
# Cookie secret: MUST be exactly 32 characters
# Generate with: openssl rand -base64 24
cookie_secret: "ePBznSrMg27mFSjyMsbEc8p4TB4Kch6y"
# Set to true if using HTTPS (via reverse proxy), false for HTTP
cookie_secure: false
# Session cookie max age (1 day = 86400 seconds)
cookie_max_age: 86400
# Optional: restrict cookie to specific domain
# cookie_domain: "headscale.example.com"
# Data persistence path (must be mounted in Docker)
data_path: "/var/lib/headplane"
# Headscale integration settings
headscale:
# Headscale API URL (use container name for Docker network)
url: "http://headscale:8080"
# Optional: public URL if different from API URL
# public_url: "https://headscale.example.com"
# Path to Headscale config file (must be shared volume)
config_path: "/etc/headscale/config.yaml"
# Strict config validation (recommended)
config_strict: true
# Path to DNS extra records file (optional but recommended)
dns_records_path: "/etc/headscale/dns_records.json"
# Optional: path to TLS cert if Headscale uses custom TLS
# tls_cert_path: "/var/lib/headplane/tls.crt"
# Integration with container orchestration
integration:
# Headplane Agent (for SSH and node info)
agent:
# Enable the agent for advanced features
enabled: false
# Pre-auth key for agent to join Tailnet
# Generate with: headscale preauthkeys create --user <user> --expiration 90d
# pre_authkey: "<your-preauth-key>"
# Optional: custom hostname for agent
# host_name: "headplane-agent"
# Cache settings
# cache_ttl: 60
# cache_path: "/var/lib/headplane/agent_cache.json"
# Working directory for agent data
# work_dir: "/var/lib/headplane/agent"
# Docker integration (for Headscale restarts on config changes)
docker:
enabled: true
# Label to identify Headscale container
container_label: "me.tale.headplane.target=headscale"
# Optional: fallback to container name if label not found
# container_name: "headscale"
# Docker socket path
socket: "unix:///var/run/docker.sock"
# Kubernetes integration (alternative to Docker)
kubernetes:
enabled: false
# validate_manifest: true
# pod_name: "headscale"
# Native process integration (non-containerized deployments)
proc:
enabled: false
# OIDC (Single Sign-On) configuration - OPTIONAL
# Uncomment and configure if you want SSO login
# oidc:
# issuer: "https://accounts.google.com"
#
# # Client ID and secret from your OIDC provider
# client_id: "your-client-id.apps.googleusercontent.com"
# client_secret: "<your-client-secret>"
#
# # Or read secret from file
# # client_secret_path: "${CREDENTIALS_DIRECTORY}/oidc_client_secret"
#
# # OAuth scopes
# # scope: "openid email profile"
#
# # Headscale API key for OIDC users
# # Generate with: headscale apikeys create --expiration 999d
# # headscale_api_key: "<your-headscale-api-key>"
#
# # Redirect URI (must match your OIDC provider config)
# # redirect_uri: "https://headscale.example.com/admin/oidc/callback"
#
# # Disable regular API key login when using OIDC
# # disable_api_key_login: false
#
# # Token endpoint auth method
# # token_endpoint_auth_method: "client_secret_post"
#
# # Profile picture source: "oidc" or "gravatar"
# # profile_picture_source: "gravatar"
#
# # Manual endpoint configuration (if provider doesn't support discovery)
# # authorization_endpoint: ""
# # token_endpoint: ""
# # userinfo_endpoint: ""
#
# # Extra query parameters for authorization
# # extra_params:
# # prompt: "select_account"必须修改的项:
erver.cookie_secret: 生成32字符随机字符串
openssl rand -base64 24条件修改的项:
server.cookie_secure:使用 HTTPS(反向代理)→
true直接使用 HTTP →
false
headscale.url: 如果 Headscale 使用自定义端口,需修改
3. DNS 记录文件 (/opt/headscale/dns_records.json)
已创建空文件,Headplane 会自动管理。如需手动添加记录:
[
{
"name": "service.tailnet.local",
"type": "A",
"value": "100.64.0.10"
}
]🚀 部署步骤
1. 启动服务
cd /opt/headscale
docker compose up -d2. 检查容器状态
docker compose ps
docker compose logs -f3. 创建 Headscale API Key
# 创建永久 API Key(用于 Headplane 登录)
docker exec headscale headscale apikeys create --expiration 90d
# 查看所有 API Keys
docker exec headscale headscale apikeys list将生成的 API Key 保存,登录 Headplane 时需要。
4. 访问 Headplane
浏览器访问:http://你的服务器IP:3000/admin
使用上一步创建的 API Key 登录。
🔧 进阶配置
启用 Headplane Agent(获取节点详细信息)
1. 创建专用预授权密钥:
docker exec headscale headscale users create headplane-agent
docker exec headscale headscale preauthkeys create --user headplane-agent --reusable --expiration 365d2. 修改 /opt/headscale/plane/config.yaml:
integration:
agent:
enabled: true
pre_authkey: "<上面生成的密钥>"3. 重启 Headplane:
docker compose restart headplane配置 OIDC 单点登录(可选)
参考两个配置文件中的 OIDC 注释部分,配置步骤:
1. 在 OIDC 提供商(Google/GitHub/Keycloak等)创建应用
2. 配置回调 URL:https://你的域名/admin/oidc/callback
3. 在 Headscale 和 Headplane 配置文件中填写 OIDC 信息
4. 重要:确保两者使用相同的 client_id
### 反向代理配置(Caddy 示例)
headscale.example.com {
# Headplane Admin UI
handle /admin* {
reverse_proxy localhost:3000
}
# Headscale API
handle {
reverse_proxy localhost:8080
}
}🔍 故障排查
查看日志
# 查看所有日志
docker compose logs -f
# 只看 Headplane 日志
docker compose logs -f headplane
# 只看 Headscale 日志
docker compose logs -f headscale常见问题
1. Headplane 无法连接 Headscale
检查
headscale.url配置是否正确确认 Headscale 容器已启动:
docker compose ps
2. DNS 设置无法保存
确认
dns_records.json文件权限正确检查 Docker volume 挂载路径一致
3. Cookie 相关错误
确认
cookie_secret为32字符HTTP 环境下
cookie_secure必须为false
4. Headscale 重启失败
检查 Docker socket 挂载:
/var/run/docker.sock:/var/run/docker.sock:ro确认容器 label 正确:
me.tale.headplane.target: headscale
📊 监控
Prometheus 指标端点:http://你的服务器IP:9090/metrics
可配置 Prometheus + Grafana 进行监控。
🔄 更新
cd /opt/headscale
docker compose pull
docker compose up -d
0 RESPONSES
读者回应
把读完后的余温,留在这篇文章下面。