web3-infra

ローカル RPC は動くのに別の EC2 からは timeout する

Erigon の private JSON-RPC timeout を、client、node health、socket binding、security group に分けて確認した記録。

Jun 23, 2026
AWSSecurity GroupsErigonJSON-RPCtroubleshooting

Indexer が Ethereum JSON-RPC を retry したあとに終了した。 失敗した処理は HTTP 経由の eth_getLogs だったので、最初は node 側の問題に見えた。

rpc error after retry, exiting
Post "http://<node-private-ip>:8545": dial tcp <node-private-ip>:8545: i/o timeout

node 自体はすでに同期済みだった。 eth_syncingfalse を返し、Erigon の log には低い block age の head validated が出続けていた。 問題は Erigon の sync path ではなく、2 台の EC2 の間にあった。

用語の説明

用語意味
JSON-RPCclient が chain data を読むための HTTP API。Erigon では TCP 8545 がよく使われる。
Security groupinstance の network interface に付く AWS の firewall。inbound と outbound の通信を制御する。
Source security groupinbound rule の送信元として使う security group。その group が付いた network interface からの通信が許可される。
Socket bindingprocess が listen する address。127.0.0.1:8545 は local only、0.0.0.0:8545 は全 interface。
Timeoutclient が request を出したが、時間内に応答が返らない状態。EC2 内部通信では filtering や routing を疑うことが多い。

まず error を読む

Indexer log には見るべき情報が 2 つあった。

  • destination は node の private address と port 8545
  • error は i/o timeout

timeoutconnection refused と違う。 connection refused は、host には届いたが port で listen していない時によく出る。 i/o timeout は、有効な応答が返ってこなかった時に出る。 security group、network ACL、route、host firewall、または process が loopback にしか bind していない可能性がある。

使った command

調査の軸になった command は以下。 識別子は placeholder に置き換えている。

node と caller の instance を見る。

aws ec2 describe-instances \
  --profile <aws-profile> \
  --region <region> \
  --instance-ids <node-instance-id> \
  --query 'Reservations[].Instances[].{InstanceId:InstanceId,Name:Tags[?Key==`Name`]|[0].Value,PrivateIp:PrivateIpAddress,SecurityGroups:SecurityGroups[].GroupId,Subnet:SubnetId,Vpc:VpcId}' \
  --output json

aws ec2 describe-instances \
  --profile <aws-profile> \
  --region <region> \
  --filters 'Name=private-ip-address,Values=<caller-private-ip>' \
  --query 'Reservations[].Instances[].{InstanceId:InstanceId,Name:Tags[?Key==`Name`]|[0].Value,State:State.Name,PrivateIp:PrivateIpAddress,SecurityGroups:SecurityGroups[].GroupId,Subnet:SubnetId,Vpc:VpcId}' \
  --output json

両側の security group を読む。

aws ec2 describe-security-groups \
  --profile <aws-profile> \
  --region <region> \
  --group-ids <node-sg-id> <caller-sg-id> \
  --query 'SecurityGroups[].{GroupId:GroupId,Name:GroupName,Ingress:IpPermissions,Egress:IpPermissionsEgress}' \
  --output json

SSM 経由で node host 上の状態を見る。 wrapper command は remote shell command を実行し、command ID を返す。

aws ssm send-command \
  --profile <aws-profile> \
  --region <region> \
  --instance-ids <node-instance-id> \
  --document-name AWS-RunShellScript \
  --comment 'read-only rpc listen check' \
  --parameters commands='<json-array-of-shell-commands>'

aws ssm get-command-invocation \
  --profile <aws-profile> \
  --region <region> \
  --command-id <command-id> \
  --instance-id <node-instance-id>

node host 上で実行した shell command はこれ。

ss -lntp | egrep ':8545|:8546|:8551' || true

curl -sS -m 3 \
  -H "Content-Type: application/json" \
  --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
  http://127.0.0.1:8545

docker ps --format 'table {{.Names}}\t{{.Ports}}'

caller 側も同じ SSM wrapper で実行した。 実際の shell command はこれ。

timeout 5 bash -lc '</dev/tcp/<node-private-ip>/8545' \
  && echo tcp_ok || echo tcp_failed

curl -sS -m 5 \
  -H "Content-Type: application/json" \
  --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
  http://<node-private-ip>:8545

security group が詰まりどころだと分かったら、narrow な inbound rule を 1 本だけ追加する。

aws ec2 authorize-security-group-ingress \
  --profile <aws-profile> \
  --region <region> \
  --group-id <node-sg-id> \
  --ip-permissions 'IpProtocol=tcp,FromPort=8545,ToPort=8545,UserIdGroupPairs=[{GroupId=<caller-sg-id>,Description="JSON-RPC from application server"}]'

最後に caller-side check をもう一度実行し、tcp_oketh_blockNumber の応答を確認する。

network を変える前に node を見る

最初に node host 上で local RPC を叩いた。

curl -sS -m 3 \
  -H "Content-Type: application/json" \
  --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
  http://127.0.0.1:8545

node は block number を返した。 eth_syncingfalse だった。 log も正常だった。

head validated ... age=2s
Timings: Forkchoice Commit ... commit=1s

ここで chain sync は疑いから外れた。 node process は生きていて、local RPC は返り、Erigon は head を追えている。

port の bind address を見る

local で成功しても、別 host から届くとは限らない。 process が 127.0.0.1 だけに bind していることがある。

ss -lntp | egrep ':8545|:8546|:8551'
docker ps --format 'table {{.Names}}\t{{.Ports}}'

見る signal はこれだった。

0.0.0.0:8545
[::]:8545

container は RPC port を host の全 interface に publish していた。 もし 127.0.0.1:8545 しか出ていなければ、node または container の bind 設定を直す場面だった。 今回は binding は詰まりどころではなかった。

caller 側から確認する

caller host からは TCP connection を開けなかった。

timeout 5 bash -lc '</dev/tcp/<node-private-ip>/8545' \
  && echo tcp_ok || echo tcp_failed

結果はこうだった。

tcp_failed

同じ host から JSON-RPC を投げても timeout した。

curl -sS -m 5 \
  -H "Content-Type: application/json" \
  --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
  http://<node-private-ip>:8545

この時点で状態はかなり絞れた。

CheckResult
Node local RPCOK
Node socket binding0.0.0.0:8545
Caller to node TCPtimeout
Caller to node JSON-RPCtimeout

詰まりは 2 台の host の間にある。

security group を data path として読む

2 台の instance は同じ VPC と subnet にいた。 caller は outbound を出せる。 node 側の security group は P2P と metrics を許可していたが、caller からの JSON-RPC は許可していなかった。

private blockchain node では、この分け方は自然だ。 P2P は public、metrics は monitoring host、JSON-RPC は必要な service だけに絞る。 ここでやることは open な security group を使い回すことでも、8545 を internet に出すことでもなかった。 必要なのは 1 本の inbound rule だった。

node security group
TCP 8545
source: application server security group
description: JSON-RPC from application server

source security group を使うと、rule は IP ではなく caller の役割に寄る。 application server が置き換わっても、新しい network interface に同じ security group が付いていれば通信は通る。

caller 側から再確認する

rule を追加したあと、同じ caller-side check はこう変わった。

tcp_ok

RPC も返った。

{"jsonrpc":"2.0","id":1,"result":"0x..."}
{"jsonrpc":"2.0","id":2,"result":false}

1 つ目は current block number。 2 つ目は eth_syncing=false。 caller が node に到達でき、node も head 付近にいることを確認できた。

この順序で切り分ける

この timeout は、Erigon log、disk I/O、snapshot build に意識を持っていかれやすい。 それらを見る場面もあるが、今回の問いは network path だった。

短い順序はこれで足りた。

  1. RPC error と destination を確認する。
  2. node の local RPC を確認する。
  3. port が loopback-only ではないことを確認する。
  4. caller から TCP を開けないことを確認する。
  5. security group と意図した data path を照合する。
  6. narrow な inbound rule を 1 本追加する。
  7. 同じ TCP と RPC の command で再確認する。

private RPC は、必要な machine からだけ届く状態がいい。 local RPC が返り、remote RPC が timeout する時は、node process を触る前に network path を見る。