使用两台 VPS 部署 TeslaMate:Nginx HTTPS、Grafana 与 Tesla 中国 API 代理链路完整记录
为什么需要两台 VPS
正常情况下,TeslaMate 只需要一台 VPS 就可以部署:
TeslaMate + PostgreSQL + Grafana + Mosquitto + Nginx
但这次部署遇到的关键问题不是安装问题,而是网络出口问题。
主 VPS 上 TeslaMate 可以成功访问 Tesla 登录接口,并刷新 Token:
POST https://auth.tesla.cn/oauth2/v3/token -> 200
Refreshed api tokens
这说明 Tesla 账号 Token 本身是有效的,TeslaMate 也能正常启动。但主 VPS 访问 Tesla 中国区车辆接口时失败:
GET https://owner-api.vn.cloud.tesla.cn/api/1/products -> error: :timeout
Could not get vehicles: :timeout
手工测试也能复现:
curl -v --connect-timeout 15 https://owner-api.vn.cloud.tesla.cn/api/1/products
现象是 TCP 443 已经连上,但 TLS 握手超时:
Connected to owner-api.vn.cloud.tesla.cn port 443
TLS handshake, Client hello
SSL connection timeout
这说明问题不是 Docker、Nginx、Grafana、数据库,也不是 TeslaMate 配置错误,而是:
主 VPS 到 Tesla 中国 API 后端的网络链路不稳定或不可用
如果 TeslaMate 拿不到车辆列表,后续就会连锁出现:
TeslaMate 主页没有车辆
PostgreSQL 的 cars 表为空
Grafana 面板 No data
部分 Grafana SQL 面板因为车辆变量为空而报错
随后测试第二台 VPS,发现它可以正常访问 Tesla 中国 API:
curl -I --connect-timeout 10 https://fleet-api.prd.cn.vn.cloud.tesla.cn
curl -I --connect-timeout 10 https://owner-api.vn.cloud.tesla.cn/api/1/products
返回结果分别是:
HTTP/2 404
HTTP/2 401
这里的 404 和 401 都不是网络错误:
404说明 Fleet API 根路径可达,只是根路径没有对应资源;401说明 Owner API 可达,只是没有携带 Tesla Token。
因此最终选择使用两台 VPS:
主 VPS:继续运行 TeslaMate、Grafana、Nginx、PostgreSQL
第二台 VPS:作为访问 Tesla 中国 API 的专用出口
这样做的目的不是“让整台主 VPS 都走代理”,而是只让 TeslaMate 容器访问 Tesla API 的 HTTPS 流量走第二台 VPS。其他服务,例如 Nginx、Grafana、sub-store、系统 apt、SSH 等,都保持原来的网络出口。
最终解决方案是:
第二台 VPS 上运行 tinyproxy
主 VPS 用 systemd 保持 SSH 隧道
主 VPS 用 redsocks 接收透明代理流量
主 VPS 用 iptables 只劫持 TeslaMate 容器的 443 流量
一、最终架构
本次使用两台服务器:
| 角色 | IP | 用途 |
|---|---|---|
| 主服务器 / Evoxt VPS | <MAIN_VPS_IP> |
运行 TeslaMate、Grafana、PostgreSQL、Mosquitto、Nginx |
| 代理出口服务器 | <PROXY_VPS_IP> |
运行 tinyproxy,帮助主服务器访问 Tesla 中国 API |
域名规划:
| 域名 | 指向 | 用途 |
|---|---|---|
<TESLAMATE_DOMAIN> |
<MAIN_VPS_IP> |
TeslaMate Web |
<GRAFANA_DOMAIN> |
<MAIN_VPS_IP> |
Grafana 仪表盘 |
最终流量路径:
浏览器
↓
https://<TESLAMATE_DOMAIN>
https://<GRAFANA_DOMAIN>
↓
Nginx + HTTPS + Basic Auth
↓
TeslaMate / Grafana
TeslaMate 访问 Tesla 中国 API 的路径:
TeslaMate 容器
↓
iptables 只劫持 TeslaMate 容器的 443 流量
↓
redsocks :12345
↓
Evoxt 本机 172.17.0.1:7890
↓
systemd 托管的 SSH 隧道
↓
<PROXY_VPS_IP>:127.0.0.1:8888
↓
tinyproxy
↓
Tesla 中国 API
这套结构的核心目标是:
只有 TeslaMate 容器访问 Tesla API 时走第二台服务器;
Evoxt 上的 Nginx、Grafana、PostgreSQL、Mosquitto、sub-store 等其他服务不走代理。
二、两台 VPS 之间是如何连接的
两台 VPS 之间不是直接把代理端口暴露到公网,而是通过 SSH 隧道连接。
设计原则:
第二台 VPS 的 tinyproxy 只监听 127.0.0.1:8888
主 VPS 通过 SSH 隧道把 172.17.0.1:7890 转发到第二台 VPS 的 127.0.0.1:8888
TeslaMate 容器的 443 流量被 iptables 转给 redsocks
redsocks 再通过 172.17.0.1:7890 进入 SSH 隧道
这样第二台 VPS 不会变成公开代理,主 VPS 的其他服务也不会被全局代理影响。
简化链路如下:
TeslaMate 容器
↓ iptables 只匹配 TeslaMate 容器 IP
redsocks :12345
↓
172.17.0.1:7890
↓ SSH 隧道
第二台 VPS 127.0.0.1:8888
↓ tinyproxy
Tesla 中国 API
三、部署 TeslaMate 主服务
1. 检查端口占用
主 VPS 上先检查 3000 和 4000 是否被占用:
ss -lntp | grep -E ':3000|:4000'
实际情况中,3000 已经被 sub-store 占用:
sub-store xream/sub-store 0.0.0.0:3000->3000/tcp
所以不能让 Grafana 继续占用宿主机 3000。
最终端口规划:
| 服务 | 容器内部端口 | 宿主机端口 |
|---|---|---|
| TeslaMate | 4000 |
127.0.0.1:14000 |
| Grafana | 3000 |
127.0.0.1:13000 |
| sub-store | 3000 |
0.0.0.0:3000 |
2. 创建目录
mkdir -p /opt/teslamate
cd /opt/teslamate
3. 创建 .env
生成随机密钥:
openssl rand -hex 32
openssl rand -hex 24
创建 .env:
nano .env
内容示例:
TM_ENCRYPTION_KEY=替换成第一条随机字符串
TM_DB_USER=teslamate
TM_DB_PASS=替换成第二条随机字符串
TM_DB_NAME=teslamate
收紧权限:
chmod 600 .env
4. 创建 docker-compose.yml
nano docker-compose.yml
内容:
services:
teslamate:
image: teslamate/teslamate:latest
restart: always
depends_on:
- database
- mosquitto
environment:
- ENCRYPTION_KEY=${TM_ENCRYPTION_KEY}
- DATABASE_USER=${TM_DB_USER}
- DATABASE_PASS=${TM_DB_PASS}
- DATABASE_NAME=${TM_DB_NAME}
- DATABASE_HOST=database
- MQTT_HOST=mosquitto
- CHECK_ORIGIN=true
- VIRTUAL_HOST=<TESLAMATE_DOMAIN>
- TZ=Asia/Shanghai
ports:
- 127.0.0.1:14000:4000
volumes:
- ./import:/opt/app/import
cap_drop:
- all
database:
image: postgres:18-trixie
restart: always
environment:
- POSTGRES_USER=${TM_DB_USER}
- POSTGRES_PASSWORD=${TM_DB_PASS}
- POSTGRES_DB=${TM_DB_NAME}
volumes:
- teslamate-db:/var/lib/postgresql
grafana:
image: teslamate/grafana:latest
restart: always
depends_on:
- database
environment:
- DATABASE_USER=${TM_DB_USER}
- DATABASE_PASS=${TM_DB_PASS}
- DATABASE_NAME=${TM_DB_NAME}
- DATABASE_HOST=database
- GF_SERVER_DOMAIN=<GRAFANA_DOMAIN>
- GF_SERVER_ROOT_URL=https://<GRAFANA_DOMAIN>/
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=${TM_DB_PASS}
- GF_AUTH_ANONYMOUS_ENABLED=false
- TZ=Asia/Shanghai
ports:
- 127.0.0.1:13000:3000
volumes:
- teslamate-grafana-data:/var/lib/grafana
mosquitto:
image: eclipse-mosquitto:2
restart: always
command: mosquitto -c /mosquitto-no-auth.conf
volumes:
- mosquitto-conf:/mosquitto/config
- mosquitto-data:/mosquitto/data
volumes:
teslamate-db:
teslamate-grafana-data:
mosquitto-conf:
mosquitto-data:
检查配置:
docker compose config
启动:
docker compose up -d
检查容器:
docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}"
本机测试:
curl -I http://127.0.0.1:14000
curl -I http://127.0.0.1:13000
看到 302 Found 是正常的,表示服务已启动。
四、配置 Nginx 反向代理、HTTPS 与 Basic Auth
1. 创建 Basic Auth 密码
apt update
apt install -y apache2-utils
htpasswd -c /etc/nginx/.htpasswd-teslamate <BASIC_AUTH_USER>
2. 创建 Nginx 配置
nano /etc/nginx/sites-available/teslamate.conf
内容:
server {
listen 80;
server_name <TESLAMATE_DOMAIN>;
location / {
auth_basic "TeslaMate";
auth_basic_user_file /etc/nginx/.htpasswd-teslamate;
proxy_pass http://127.0.0.1:14000;
proxy_http_version 1.1;
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_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
server {
listen 80;
server_name <GRAFANA_DOMAIN>;
location / {
auth_basic "Grafana";
auth_basic_user_file /etc/nginx/.htpasswd-teslamate;
proxy_pass http://127.0.0.1:13000;
proxy_http_version 1.1;
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_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
启用配置:
ln -s /etc/nginx/sites-available/teslamate.conf /etc/nginx/sites-enabled/teslamate.conf
nginx -t
systemctl reload nginx
测试 Basic Auth:
curl -I -H "Host: <TESLAMATE_DOMAIN>" http://127.0.0.1
curl -I -H "Host: <GRAFANA_DOMAIN>" http://127.0.0.1
正常返回:
HTTP/1.1 401 Unauthorized
3. 申请 HTTPS 证书
先确认 DNS:
dig +short <TESLAMATE_DOMAIN>
dig +short <GRAFANA_DOMAIN>
应返回主 VPS IP:
<MAIN_VPS_IP>
<MAIN_VPS_IP>
申请证书:
certbot --nginx -d <TESLAMATE_DOMAIN> -d <GRAFANA_DOMAIN>
测试:
curl -I https://<TESLAMATE_DOMAIN>
curl -I https://<GRAFANA_DOMAIN>
正常返回 401 Unauthorized,表示 HTTPS 与 Basic Auth 都生效。
五、排查 TeslaMate 无车辆、Grafana No Data
安装完成后,TeslaMate 登录 Token 成功,但主页没有车辆,Grafana 显示 No data 或 SQL 错误。
查看日志:
cd /opt/teslamate
docker compose logs --tail=200 teslamate
关键日志:
POST https://auth.tesla.cn/oauth2/v3/token -> 200
Scheduling token refresh in 6 h
GET https://owner-api.vn.cloud.tesla.cn/api/1/products -> error: :timeout
Could not get vehicles: :timeout
这说明:
Token 成功;
车辆列表拉取失败;
数据库 cars 表为空;
Grafana 没有数据。
测试主 VPS 到 Tesla API:
curl -v --connect-timeout 15 https://owner-api.vn.cloud.tesla.cn/api/1/products
curl -v --connect-timeout 15 https://fleet-api.prd.cn.vn.cloud.tesla.cn
现象:
TCP 443 连接成功
TLS Client Hello 发出
SSL connection timeout
因此判断:主 VPS 到 Tesla 中国 API 的链路不正常。
六、在第二台 VPS 上安装 tinyproxy
第二台 VPS:<PROXY_VPS_IP>
1. 测试 Tesla API
curl -I --connect-timeout 10 https://fleet-api.prd.cn.vn.cloud.tesla.cn
curl -I --connect-timeout 10 https://owner-api.vn.cloud.tesla.cn/api/1/products
正常结果:
HTTP/2 404
HTTP/2 401
2. 安装 tinyproxy
apt update
apt install -y tinyproxy
cp /etc/tinyproxy/tinyproxy.conf /etc/tinyproxy/tinyproxy.conf.bak
nano /etc/tinyproxy/tinyproxy.conf
关键配置:
Port 8888
Listen 127.0.0.1
Allow 127.0.0.1
重启:
systemctl restart tinyproxy
systemctl enable tinyproxy
systemctl status tinyproxy --no-pager
检查监听:
ss -lntp | grep 8888
正常:
127.0.0.1:8888
测试代理:
curl -I -x http://127.0.0.1:8888 --connect-timeout 10 https://owner-api.vn.cloud.tesla.cn/api/1/products
正常:
HTTP/1.0 200 Connection established
HTTP/2 401
七、在主 VPS 上建立 SSH 隧道
主 VPS 需要通过 SSH 隧道访问第二台 VPS 的本地 tinyproxy。
1. 确认 Docker 网桥地址
ip -4 addr show docker0 | grep -oP '(?<=inet\s)\d+(\.\d+){3}'
结果:
172.17.0.1
2. 创建 SSH 密钥
在主 VPS 上执行:
ssh-keygen -t ed25519 -f /root/.ssh/teslamate_tunnel -N ""
查看公钥:
cat /root/.ssh/teslamate_tunnel.pub
把公钥加入第二台 VPS 的:
/root/.ssh/authorized_keys
权限:
chmod 700 /root/.ssh
chmod 600 /root/.ssh/authorized_keys
测试免密:
ssh -i /root/.ssh/teslamate_tunnel root@<PROXY_VPS_IP> "echo ok"
3. 用 systemd 托管 SSH 隧道
创建服务:
nano /etc/systemd/system/teslamate-proxy-tunnel.service
内容:
[Unit]
Description=TeslaMate SSH tunnel to <PROXY_VPS_IP> tinyproxy
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=root
ExecStart=/usr/bin/ssh -i /root/.ssh/teslamate_tunnel -N -o ExitOnForwardFailure=yes -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -o StrictHostKeyChecking=accept-new -L 172.17.0.1:7890:127.0.0.1:8888 root@<PROXY_VPS_IP>
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
启动:
systemctl daemon-reload
systemctl enable --now teslamate-proxy-tunnel
检查:
systemctl status teslamate-proxy-tunnel --no-pager
ss -lntp | grep 7890
正常:
Active: active (running)
172.17.0.1:7890
测试链路:
curl -I -x http://172.17.0.1:7890 --connect-timeout 10 https://owner-api.vn.cloud.tesla.cn/api/1/products
正常:
HTTP/1.0 200 Connection established
HTTP/2 401
八、为什么不能只给 TeslaMate 配 HTTP_PROXY
尝试在 docker-compose.yml 的 TeslaMate 服务里加入:
- HTTP_PROXY=http://172.17.0.1:7890
- HTTPS_PROXY=http://172.17.0.1:7890
但 TeslaMate 仍然请求失败:
GET https://owner-api.vn.cloud.tesla.cn/api/1/products -> error: :timeout
因此判断:TeslaMate 本体没有按预期使用这两个环境变量访问 Tesla API。
最终改用透明代理:
iptables 定向转发 TeslaMate 容器的 443 流量
↓
redsocks
↓
HTTP CONNECT 代理
九、配置 redsocks 透明代理
1. 安装 redsocks
在主 VPS 上执行:
apt update
apt install -y redsocks
cp /etc/redsocks.conf /etc/redsocks.conf.bak
2. 修改配置
nano /etc/redsocks.conf
内容:
base {
log_debug = off;
log_info = on;
log = "syslog:daemon";
daemon = on;
redirector = iptables;
}
redsocks {
local_ip = 0.0.0.0;
local_port = 12345;
ip = 172.17.0.1;
port = 7890;
type = http-connect;
}
启动:
systemctl restart redsocks
systemctl enable redsocks
systemctl status redsocks --no-pager
检查:
ss -lntp | grep 12345
正常:
0.0.0.0:12345
十、只劫持 TeslaMate 容器的 HTTPS 流量
1. 获取 TeslaMate 容器 IP
docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' teslamate-teslamate-1
示例:
172.18.0.4
2. 添加 iptables 规则
TM_IP=$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' teslamate-teslamate-1)
echo "TeslaMate container IP: $TM_IP"
iptables -t nat -N TESLAMATE_REDSOCKS 2>/dev/null || true
iptables -t nat -F TESLAMATE_REDSOCKS
iptables -t nat -A TESLAMATE_REDSOCKS -d 0.0.0.0/8 -j RETURN
iptables -t nat -A TESLAMATE_REDSOCKS -d 10.0.0.0/8 -j RETURN
iptables -t nat -A TESLAMATE_REDSOCKS -d 127.0.0.0/8 -j RETURN
iptables -t nat -A TESLAMATE_REDSOCKS -d 169.254.0.0/16 -j RETURN
iptables -t nat -A TESLAMATE_REDSOCKS -d 172.16.0.0/12 -j RETURN
iptables -t nat -A TESLAMATE_REDSOCKS -d 192.168.0.0/16 -j RETURN
iptables -t nat -A TESLAMATE_REDSOCKS -d 224.0.0.0/4 -j RETURN
iptables -t nat -A TESLAMATE_REDSOCKS -p tcp --dport 443 -j REDIRECT --to-ports 12345
iptables -t nat -D PREROUTING -s "$TM_IP" -p tcp --dport 443 -j TESLAMATE_REDSOCKS 2>/dev/null || true
iptables -t nat -A PREROUTING -s "$TM_IP" -p tcp --dport 443 -j TESLAMATE_REDSOCKS
解释:
只匹配来源为 TeslaMate 容器 IP 的 443 流量;
排除内网、Docker 网段、本机地址;
只把公网 HTTPS 流量转给 redsocks。
所以不会影响:
- Nginx
- Grafana
- PostgreSQL
- Mosquitto
- sub-store
- 主机系统自身的 apt、curl、ssh
3. 检查规则
iptables -t nat -L PREROUTING -n -v --line-numbers | grep TESLAMATE
iptables -t nat -L TESLAMATE_REDSOCKS -n -v
应看到:
TESLAMATE_REDSOCKS tcp -- 172.18.0.4 0.0.0.0/0 tcp dpt:443
REDIRECT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:443 redir ports 12345
十一、重启 TeslaMate 并验证
cd /opt/teslamate
docker compose restart teslamate
docker compose logs --tail=200 teslamate
如果成功,之前的:
Could not get vehicles: :timeout
应该消失,TeslaMate 主页会显示车辆。之后 Grafana 中的 No data 也会随着数据采集逐步恢复。
十二、持久化与开机自启
1. 持久化 iptables
apt install -y iptables-persistent
netfilter-persistent save
systemctl enable netfilter-persistent
以后每次修改 iptables 后执行:
netfilter-persistent save
2. 确认服务开机自启
systemctl is-enabled teslamate-proxy-tunnel
systemctl is-enabled redsocks
systemctl is-enabled docker
应返回:
enabled
如未启用:
systemctl enable teslamate-proxy-tunnel
systemctl enable redsocks
systemctl enable docker
十三、常用排查命令
1. 检查 TeslaMate 日志
cd /opt/teslamate
docker compose logs --tail=100 teslamate
2. 检查 SSH 隧道
systemctl status teslamate-proxy-tunnel --no-pager
ss -lntp | grep 7890
3. 检查 tinyproxy 链路
curl -I -x http://172.17.0.1:7890 --connect-timeout 10 https://owner-api.vn.cloud.tesla.cn/api/1/products
正常:
HTTP/1.0 200 Connection established
HTTP/2 401
4. 检查 redsocks
systemctl status redsocks --no-pager
ss -lntp | grep 12345
5. 检查 iptables 计数
iptables -t nat -L TESLAMATE_REDSOCKS -n -v
如果 REDIRECT 行的 pkts/bytes 在增长,说明 TeslaMate 的 HTTPS 流量正在被透明代理接管。
6. 检查数据库是否已有车辆
cd /opt/teslamate
docker compose exec database psql -U teslamate -d teslamate -c "select id, eid, vid, name, model, inserted_at from cars;"
十四、安全注意事项
1. 不要直接开放 TeslaMate / Grafana 端口
不要使用:
ports:
- 4000:4000
- 3000:3000
应使用:
ports:
- 127.0.0.1:14000:4000
- 127.0.0.1:13000:3000
2. tinyproxy 不要监听公网
第二台服务器上的 tinyproxy 必须是:
127.0.0.1:8888
不要是:
0.0.0.0:8888
否则会变成公开代理。
3. Basic Auth 与 Grafana 密码要使用强密码
访问 Grafana 实际有两层认证:
第一层:Nginx Basic Auth
第二层:Grafana 登录
4. 不要使用公开免费代理
TeslaMate 涉及:
- Tesla Token
- 车辆位置
- 行程
- 充电记录
- 电池信息
不要把这些流量交给来路不明的免费代理。
十五、总结
本次部署的关键不是 TeslaMate 安装,而是网络路径处理。
正常安装 TeslaMate 很简单:
Docker Compose + PostgreSQL + Grafana + Mosquitto
真正的问题是主 VPS 到 Tesla 中国 API 的 TLS 握手失败。解决方式是:
找一台能访问 Tesla 中国 API 的第二台 VPS;
在第二台 VPS 上运行 tinyproxy;
主 VPS 用 systemd 托管 SSH 隧道;
再用 redsocks + iptables 只透明代理 TeslaMate 容器的 443 流量。
最终实现:
TeslaMate 能访问 Tesla 中国 API;
Grafana 正常获取数据;
主 VPS 其他服务不走代理;
代理服务不暴露公网。