使用两台 VPS 部署 TeslaMate

使用两台 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

这里的 404401 都不是网络错误:

  • 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 上先检查 30004000 是否被占用:

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 其他服务不走代理;
代理服务不暴露公网。
Built with Hugo
Theme Stack designed by Jimmy