EC2实例LND节点SSL证书主机名不匹配问题求助
错误信息
HTTPSConnectionPool(host='ec2-aa-aaa-aaa-aa.eu-central-1.compute.amazonaws.com', port=8080): Max retries exceeded with url: /v1/getinfo (Caused by SSLError(CertificateError("hostname 'ec2-aa-aaa-aa-aa.eu-central-1.compute.amazonaws.com' doesn't match either of 'ip-bbb-bb-bb-bb', 'localhost', 'unix'...))
其中ec2-aa-aaa-aaa-aa.eu-central-1.compute.amazonaws.com是EC2公网域名,ip-bbb-bb-bb-bb是EC2私有IP。
原因分析
LND默认生成的TLS证书仅包含私有IP、localhost、unix作为SAN(Subject Alternative Names),当你用公网域名/公网IP访问LND的REST接口时,SSL验证会检查主机名是否在证书的SAN列表中,不匹配就会抛出这个错误。
解决方案
1. 重新生成包含公网IP/域名的LND证书(推荐生产环境使用)
- 停止LND服务:
sudo systemctl stop lnd - 删除旧的TLS证书文件:
rm ~/.lnd/tls.cert ~/.lnd/tls.key - 编辑LND配置文件
~/.lnd/lnd.conf,添加以下配置项:tlsextraip=<你的EC2公网IP> tlsextradomain=<你的EC2公网域名> - 重启LND服务,它会自动生成包含新SAN的证书:
sudo systemctl start lnd - 重新通过SFTP下载新的
tls.cert到你的Django项目目录,替换旧证书后再测试API访问。
2. 临时跳过SSL验证(仅用于测试,生产环境禁用)
如果只是临时测试,可以在Django的请求代码中关闭SSL验证(以requests库为例):
import requests response = requests.get( "https://ec2-aa-aaa-aaa-aa.eu-central-1.compute.amazonaws.com:8080/v1/getinfo", headers={"Grpc-Metadata-macaroon": "<你的macaroon编码>"}, verify=False # 跳过验证,生产环境不要用 )
⚠️ 注意:这种方法会暴露中间人攻击风险,绝对不能在生产环境使用。
3. 配置Nginx反向代理(适合生产环境,使用可信证书)
在EC2实例上安装Nginx,申请Let's Encrypt证书(匹配公网域名),然后配置反向代理到LND的8080端口:
- 安装Nginx和Certbot:
sudo apt update && sudo apt install nginx certbot python3-certbot-nginx - 申请证书:
sudo certbot --nginx -d ec2-aa-aaa-aaa-aa.eu-central-1.compute.amazonaws.com - 修改Nginx配置文件(比如
/etc/nginx/sites-available/lnd):server { listen 443 ssl; server_name ec2-aa-aaa-aaa-aa.eu-central-1.compute.amazonaws.com; ssl_certificate /etc/letsencrypt/live/ec2-aa-aaa-aaa-aa.eu-central-1.compute.amazonaws.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/ec2-aa-aaa-aaa-aa.eu-central-1.compute.amazonaws.com/privkey.pem; location / { proxy_pass http://localhost:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; # 传递macaroon header,或者在Django请求中携带 } } - 启用配置并重启Nginx:
sudo ln -s /etc/nginx/sites-available/lnd /etc/nginx/sites-enabled/ sudo systemctl restart nginx
之后Django访问Nginx的443端口即可,证书是可信且匹配公网域名的。
4. 修改本地hosts文件(仅本地测试用)
在本地机器的hosts文件中添加映射,把公网域名指向EC2私有IP:
ip-bbb-bb-bb-bb ec2-aa-aaa-aaa-aa.eu-central-1.compute.amazonaws.com
这样访问公网域名时会解析到私有IP,匹配证书中的SAN,但仅适合本地测试,其他机器无法使用。
你提供的EC2安全组规则
– sgr-0b6e4646f963ac348 All All 0.0.0.0/0 – sgr-0c9433a6bdbc5c986 8333 TCP ::/0 – sgr-0ab3df01e90304321 8333 TCP 0.0.0.0/0 – sgr-002bc487360643667 443 TCP 0.0.0.0/0 – sgr-08bad3cd41c9d3365 80 TCP 0.0.0.0/0 – sgr-005fa2007f73f3466 28333 TCP 0.0.0.0/0 – sgr-0fba32539580d3071 0 - 65535 TCP 0.0.0.0/0 – sgr-092c6691e0dbdf9d3 All ICMP 0.0.0.0/0 – sgr-000bdb5bee77e7314 8000 TCP 0.0.0.0/0 – sgr-0112ef3fdd16c2b75 22 TCP 0.0.0.0/0
你使用的SSH连接代码
import boto3 import paramiko import os import base64, codecs # Define the instance's ID and the key pair name instance_id = "<i-blahblahblah>" key_name = "<some_keyname>" # Create a session using your AWS credentials session = boto3.Session( aws_access_key_id="<some_access_key_is>", aws_secret_access_key="<some_aws_secret_access_key>", region_name="eu-central-1", ) # Use the session to create an EC2 client ec2 = session.client("ec2") # Retrieve the IP address of the instance instance = ec2.describe_instances(InstanceIds=[instance_id])["Reservations"][0][ "Instances" ][0] ip_address = instance["PublicIpAddress"] print(ip_address) # Use the session to create a SSM client ssm = session.client("ssm") # Start a session to the instance using SSM response = ssm.start_session(Target=instance_id) # Use paramiko to SSH into the instance ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) key_name = "<path_to_key>" ssh.connect(ip_address, username="ubuntu", key_filename=f"{key_name}.pem") # Execute the command to retrieve the environment variable stdin, stdout, stderr = ssh.exec_command("echo $VARNAME") my_variable = stdout.read().strip().decode() # Read the output of the command sftp_client = ssh.open_sftp() remote_path = "/home/ubuntu/.lnd/data/chain/bitcoin/mainnet/admin.macaroon" remote_file = sftp_client.open(remote_path, "r") # Read contents of file file_contents = remote_file.read() encoded_hex = codecs.encode(file_contents, "hex") encoded_string = encoded_hex.decode("utf-8") # Now you can use the decoded string in your API print(encoded_string) # Get the path to the certificate file on the EC2 instance remote_cert_file = "/home/ubuntu/.lnd/tls.cert" # Transfer the certificate file from the EC2 instance to the local machine local_cert_file = "tls.cert" sftp = ssh.open_sftp() sftp.get(remote_cert_file, local_cert_file) # Specify the path to the local certificate file for SSL verification cert_file_path = os.path.abspath(local_cert_file) print(cert_file_path) sftp.close() # Close file and SFTP client remote_file.close() stdin.close() # Close the SSH connection and SSM session ssh.close() ssm.terminate_session(SessionId=response["SessionId"]) response = {"file_contents": file_contents}
内容的提问来源于stack exchange,提问作者Pau1aAm7

