RDS 接続できない・タイムアウトの原因と切り分け手順

目次
  1. 結論:まずセキュリティグループのインバウンドとサブネットの経路を確認する
  2. 接続タイムアウトの切り分け手順(上から順に確認する)
  3. タイムアウト以外のエラーメッセージの読み分け方
  4. 普段はつながるのに時々タイムアウトする場合の調査ポイント
  5. よくある質問
  6. 再発防止のための設定と設計のチェックポイント
  7. さらに学ぶには
このテーマのまとめRDS・Aurora運用トラブルの切り分け|接続断と性能劣化
本記事にはプロモーション(広告)が含まれています。

RDSやAuroraに接続しようとしてプロンプトが返らず、数十秒待たされた末にタイムアウトする。DBエンジニアが頻繁に遭遇するトラブルで、原因の大半はネットワーク側にあります。

タイムアウトと即座に返るエラーは、切り分けの起点が違います。タイムアウトはTCPのSYNに応答が返らない状態で、パケットがどこかで捨てられている(あるいは戻ってこない)ことを意味します。パスワードやデータベース名の誤りなら接続自体は成立するため、エラーはすぐ返ってきます。この違いだけで確認範囲は大きく絞れます。

上から順に確認すれば原因が特定できる手順をまとめました。

結論:まずセキュリティグループのインバウンドとサブネットの経路を確認する

接続タイムアウトの原因を頻度順に並べると、おおむね次のようになります。

  1. DBインスタンスにアタッチされたセキュリティグループのインバウンドルールで、接続元が許可されていない
  2. 接続元とDBが別VPC・別サブネットで、ルーティングやピアリングが通っていない
  3. パブリックアクセス設定とサブネットのルートテーブルが噛み合っていない(インターネット経由で接続する場合)
  4. ネットワークACLで戻りの通信が遮断されている
  5. 接続先エンドポイントやポートが間違っている(古いエンドポイント、リーダーとライターの混同など)
  6. DBインスタンスがavailable以外のステータス(stopped、modifying、再起動中)になっている

1と2で大部分が説明できます。セキュリティグループはステートフルなので、インバウンドでDBポートを許可すれば戻りの通信は自動で通ります。一方のネットワークACLはステートレスで、インバウンドとアウトバウンドの両方にルールが必要です。デフォルトのACLは全許可なので、カスタムACLを自分で作っている環境だけ疑ってください。

RDSのDBインスタンスは原則としてVPC内に配置され、インターネットから直接到達できるのは「パブリックアクセス: あり」「パブリックサブネット配置」「セキュリティグループ許可」の三つが揃ったときだけです。一つでも欠けるとタイムアウトします。

接続タイムアウトの切り分け手順(上から順に確認する)

手順1:接続元・エンドポイント・ポートを正確に確定させる

調査の前に、どこからどこへどのポートでつなごうとしているのかを文字列レベルで確定させます。ここが曖昧なままSGを見ても時間を無駄にします。

# DB インスタンスの基本情報をまとめて確認
aws rds describe-db-instances \
  --db-instance-identifier my-db \
  --query 'DBInstances[0].{Status:DBInstanceStatus,Endpoint:Endpoint.Address,Port:Endpoint.Port,Public:PubliclyAccessible,VpcSG:VpcSecurityGroups,SubnetGroup:DBSubnetGroup.DBSubnetGroupName,AZ:AvailabilityZone}' \
  --output json
# Aurora クラスターの場合はクラスター側のエンドポイントも確認
aws rds describe-db-clusters \
  --db-cluster-identifier my-cluster \
  --query 'DBClusters[0].{Endpoint:Endpoint,Reader:ReaderEndpoint,Port:Port,Status:Status}' \
  --output json

まずDBInstanceStatusがavailableかを見ます。stoppedなら接続できません。RDSは最大7日で自動起動しますが、その間はつながりません。ポートも既定値とは限りません。PostgreSQLは5432、MySQLは3306が既定ですが、作成時に変更している環境は珍しくなく、SGで5432を開けているのに実体は5433だったという例があります。エンドポイントの名前解決もnslookupやdigでAレコードが返るか確認してください。

dig +short my-db.xxxxxxxx.ap-northeast-1.rds.amazonaws.com

返ってくるIPがプライベートIP(10.x、172.16〜31.x、192.168.x)なら、そのVPC内部からしか到達できません。VPC外から接続していたなら、ここで原因が確定します。逆にVPC内から接続しているのにパブリックIPが返る場合は、DNS解決がVPCのRoute 53 Resolverを経由していない疑いがあります。エンドポイントのDNSはVPC内からはプライベートIPを返す仕様です。オンプレのDNSを参照している、enableDnsHostnamesやenableDnsSupportが無効といった原因を確認します。

手順2:セキュリティグループのインバウンドルールを確認する

手順1で得たVPCセキュリティグループIDを使って、インバウンドルールを確認します。

aws ec2 describe-security-groups \
  --group-ids sg-0123456789abcdef0 \
  --query 'SecurityGroups[0].IpPermissions' \
  --output json

マネジメントコンソールなら、RDSのDBインスタンス詳細画面の「接続とセキュリティ」タブからVPCセキュリティグループのリンクをたどり、「インバウンドルール」で確認できます。

見るべき点は三つあります。DBポートに対するルール(「タイプ: PostgreSQL / ポート: 5432」など)が存在するか。ソースに接続元が含まれているか。ソースが別のセキュリティグループID(アプリサーバーのSGなど)になっている場合は、接続元のEC2やLambdaがそのSGを実際にアタッチしているかを確認します。SG参照は便利な反面、スケールアウトで新しく作ったインスタンスにSGを付け忘れる事故が起きやすい箇所です。

CIDR指定の誤りも多く見かけます。10.0.1.0/24と書いてあるのに接続元が10.0.2.xにいる、/32のホスト指定が古いIPのまま、といったケース。自宅や社内から接続している場合は、プロバイダやVPNのグローバルIPが変わっていることもあります。

接続元がEC2なら、EC2側のアウトバウンドルールも見ます。既定は全許可ですが、セキュリティ要件で絞っている環境ではDBポートへの送信が許可されていない可能性があります。

手順3:サブネット・ルートテーブルとパブリックアクセス設定を確認する

同一VPC内でSGにも問題がない場合は、サブネットとルーティングを見ます。

# DB サブネットグループに含まれるサブネットを確認
aws rds describe-db-subnet-groups \
  --db-subnet-group-name my-subnet-group \
  --query 'DBSubnetGroups[0].Subnets[].{Subnet:SubnetIdentifier,AZ:SubnetAvailabilityZone.Name}' \
  --output table
# そのサブネットに紐づくルートテーブルを確認
aws ec2 describe-route-tables \
  --filters "Name=association.subnet-id,Values=subnet-0123456789abcdef0" \
  --query 'RouteTables[].Routes' \
  --output json

判断基準は接続経路のパターンごとに分かれます。

同一VPC内のEC2から接続する場合、サブネット間はローカルルート(10.0.0.0/16 → local)で到達するため、ルーティングは通常問題になりません。SGかネットワークACLを疑います。

別VPCから接続する場合は、VPCピアリングまたはTransit Gatewayが必要です。加えて両側のルートテーブルに相手のCIDRへのルートが入っている必要があり、片側だけ設定してタイムアウトするのは典型例です。

オンプレミスや別アカウントからVPNやDirect Connect経由でつなぐなら、ルートテーブルに仮想プライベートゲートウェイ(VGW)やTGWへのルートがあるか、オンプレ側のファイアウォールでDBポートが開いているかを確認します。

インターネット経由でローカルPCから直接つなぐ場合は、次の三つすべてが必要です。

  • DBインスタンスのPubliclyAccessibleがtrue
  • DBが配置されたサブネットのルートテーブルに0.0.0.0/0 → igw-xxxxがある(パブリックサブネット)
  • SGのインバウンドで自分のグローバルIPが許可されている

PubliclyAccessible: trueでも、サブネットにIGWへのルートがなければパブリックIPは割り当たらず、外部から到達できません。プライベートサブネット配置のまま外から接続したい場合は、踏み台EC2のSSHポートフォワードか、Session Managerのポートフォワーディングを使います。

# 踏み台経由でローカルの 15432 を RDS の 5432 に転送する例
ssh -i key.pem -N -L 15432:my-db.xxxxxxxx.ap-northeast-1.rds.amazonaws.com:5432 ec2-user@<bastion-public-ip>

# 別ターミナルから
psql -h localhost -p 15432 -U appuser -d appdb

この記事のテーマをさらに学べる本

接続経路の切り分けに必要なVPCの基礎を解説した書籍で、第2版として内容が更新されています。

楽天ブックスで詳しく見る Yahoo!ショッピングで見る

手順4:コマンドとReachability Analyzerで到達性を確認する

設定を目で追うのと並行して、TCPが実際に届くかを測ります。DBクライアントのエラーメッセージは原因を曖昧にしがちなので、まず素のTCPで確認するのがコツです。

# nc(netcat)で TCP 到達性のみ確認。タイムアウトを 5 秒に制限
nc -zv -w 5 my-db.xxxxxxxx.ap-northeast-1.rds.amazonaws.com 5432
# nc が無い場合、bash の /dev/tcp と timeout を併用
timeout 5 bash -c 'cat < /dev/null > /dev/tcp/my-db.xxxxxxxx.ap-northeast-1.rds.amazonaws.com/5432' \
  && echo "reachable" || echo "unreachable"
# PostgreSQL なら pg_isready で接続可否のみを素早く判定
pg_isready -h my-db.xxxxxxxx.ap-northeast-1.rds.amazonaws.com -p 5432 -t 5

結果の読み方は次のとおりです。

  • succeededやreachableが返るのにDBクライアントで失敗する → ネットワークは通っている。認証、SSL、データベース名を疑う
  • 5秒待って無反応 → パケットが捨てられている。SG、ACL、ルーティングの問題
  • Connection refusedが即返る → 到達はしているがそのポートで待ち受けていない。ポート番号の誤りか、別のホストに当たっている

設定が複雑で目視では追い切れない場合は、VPCのReachability Analyzerを使います。送信元(EC2のENIなど)と宛先(RDSのENIまたはIP)、プロトコルとポートを指定すると、経路上のどのコンポーネントで遮断されているかを名指しで返します。CLIからも実行できます。

# 経路を作成して解析を開始
aws ec2 create-network-insights-path \
  --source eni-0aaa111bbb222ccc3 \
  --destination-ip 10.0.2.45 \
  --protocol tcp \
  --destination-port 5432

aws ec2 start-network-insights-analysis \
  --network-insights-path-id nip-0123456789abcdef0
# 結果を取得(NetworkPathFound と ExplanationCodes を見る)
aws ec2 describe-network-insights-analyses \
  --network-insights-analysis-ids nia-0123456789abcdef0 \
  --query 'NetworkInsightsAnalyses[0].{Found:NetworkPathFound,Explanations:Explanations[].ExplanationCode}'

ELB_LISTENER_NOT_FOUND、NO_ROUTE_TO_DESTINATION、ACL_RULE、SECURITY_GROUP_RULEといったコードが返り、どのリソースのどのルールが原因か特定できます。解析ごとに課金されるため、料金は公式の料金ページで確認してください。宛先IPには、VPC内から引いたエンドポイントのプライベートIPを使うのが手軽です。

タイムアウト以外のエラーメッセージの読み分け方

タイムアウト以外のエラーが返っているなら、ネットワークは通っています。そこから先は切り分け先が変わります。代表的なものを挙げます。

timeout expired / Operation timed out / connect timed out(JDBC) TCPが到達していません。本記事の手順1〜4の範囲です。

Connection refused / could not connect to server: Connection refused ホストには届いているが、そのポートでLISTENしていない状態。ポート番号の誤りか、踏み台のポートフォワード先の間違いです。RDS本体が原因であることはまれです。

FATAL: password authentication failed for user "xxx"(PostgreSQL)/ Access denied for user(MySQL) 接続は成立しており、認証情報の問題です。Secrets Managerのローテーション後にアプリが古いパスワードをキャッシュしている、IAM認証を使うべきところでパスワード認証している、などを疑います。

FATAL: database "xxx" does not exist 接続成立。データベース名の誤りです。PostgreSQLで-dを省略すると、ユーザー名と同名のDBに接続しようとします。

FATAL: no pg_hba.conf entry for host ..., SSL off RDSのPostgreSQLでパラメータrds.force_ssl=1になっている場合に出ます。クライアント側でsslmode=require以上を指定してください。

psql "host=my-db.xxxxxxxx.ap-northeast-1.rds.amazonaws.com port=5432 dbname=appdb user=appuser sslmode=require"

FATAL: remaining connection slots are reserved for non-replication superuser connections(PostgreSQL)/ Too many connections(MySQL) max_connectionsに達しています。SGの問題ではありません。アプリ側のコネクションプール設定の見直し、RDS Proxyの導入、インスタンスクラスの見直しが対処になります。Aurora Serverless v2ではmax_connectionsがACUに依存するため、Aurora Serverless v2でmax_connectionsが変わらない原因と対処も合わせて確認してください。

SSL error: certificate verify failed / PKIX path building failed sslmode=verify-fullやJDBCの証明書検証で、クライアントがAWSのRDS CA証明書を持っていない状態です。リージョンに対応したバンドル(global-bundle.pemなど)を取得して信頼ストアに入れます。

the database system is starting up / shutting down フェイルオーバーや再起動の最中です。しばらく待って再試行します。意図しない再起動が起きている場合はRDS 証明書ローテーションで再起動される原因と対処が参考になります。

普段はつながるのに時々タイムアウトする場合の調査ポイント

常時つながらないケースより厄介なのが、間欠的なタイムアウトです。設定は正しいので、原因は「状態」側にあります。

フェイルオーバーが一つ目の候補です。Multi-AZやAuroraのフェイルオーバーではDNSの切り替えに数十秒かかり、クライアントがDNSをキャッシュしていると切り替え後も旧プライマリのIPに接続し続けてタイムアウトします。Javaならnetworkaddress.cache.ttlを30秒程度に短縮するのが定番の対処です。RDSのイベント履歴で、発生時刻とフェイルオーバーが一致するかを確認してください。

aws rds describe-events \
  --source-identifier my-db --source-type db-instance \
  --duration 1440 --query 'Events[].{Time:Date,Msg:Message}' --output table

原因の特定方法はRDS Multi-AZ フェイルオーバー原因の確認手順で詳しく扱っています。

コネクション数の上限到達も疑います。負荷ピーク時だけmax_connectionsに達し、新規接続が待たされてクライアント側のタイムアウトに至るパターン。CloudWatchのDatabaseConnectionsを上限値と並べて見ます。Lambdaの同時実行が跳ねると接続が一気に増えるので注意が必要です。

NATゲートウェイのポート枯渇も原因になります。プライベートサブネットからNAT経由で別VPCや外部のDBにつなぐ構成では、同一宛先への同時接続が増えると送信元ポートが枯渇します。CloudWatchのNATゲートウェイメトリクスErrorPortAllocationが0以外なら確定です。

アイドルタイムアウトによる切断もあります。NATゲートウェイやNLBを経由する経路では、一定時間アイドルだったTCPコネクションが通知なく破棄されます。プールが保持している無効なコネクションを再利用した時点でタイムアウトする、という形で現れます。対処はTCPキープアライブを短めに設定するか、プール側でアイドル接続の最大寿命と検証クエリを設定することです。

# Linux 側のキープアライブを短縮する例(恒久化は sysctl.d へ)
sudo sysctl -w net.ipv4.tcp_keepalive_time=200
sudo sysctl -w net.ipv4.tcp_keepalive_intvl=30
sudo sysctl -w net.ipv4.tcp_keepalive_probes=5

PostgreSQL側でも、サーバー発のキープアライブをtcp_keepalives_idleなどのパラメータで調整できます。

CPUやストレージの枯渇による応答不能も考えられます。CPUUtilizationが100%近辺に張り付いている、あるいはFreeableMemoryが枯渇してスワップが発生している状況では、接続要求の処理自体が遅延します。この場合はネットワークより性能の問題として扱い、待機イベントをPerformance Insightsで確認します。

メンテナンス・バックアップウィンドウも確認対象です。Single-AZのRDSではスナップショット取得中にI/O停止が発生し得ます。発生時刻がバックアップウィンドウと一致していないか確認します。

よくある質問

Lambdaから接続できないときはどこを確認すればいい?

VPC内のRDSに接続するには、Lambda関数自体をVPCに接続する設定が必要です。確認順は次のとおりです。

  1. 関数のVPC設定でサブネットとセキュリティグループが指定されているか(未設定ならVPC内に到達できません)
  2. 指定したLambdaのSGが、RDS側SGのインバウンドで許可されているか
  3. Lambdaを配置したサブネットからRDSのサブネットへローカルルートが通っているか
  4. 実行ロールにAWSLambdaVPCAccessExecutionRole相当のENI操作権限があるか
aws lambda get-function-configuration --function-name my-func \
  --query '{VpcConfig:VpcConfig,Timeout:Timeout}'

よくある見落としが二つあります。一つは、VPCに接続したLambdaはインターネットへの経路を失うため、Secrets Managerや他のAWS APIを呼び出している部分がタイムアウトし、DB接続エラーと誤認されること。Interface型のVPCエンドポイントかNATゲートウェイが必要です。もう一つは、LambdaのTimeoutが短すぎて、DB接続のリトライ中に関数自体が終了してしまうケースです。

Lambdaの同時実行で接続数が跳ねる構成では、RDS Proxyを挟んで接続をプールするのが定石です。

パブリックアクセスを「あり」にすればインターネットから接続できる?

それだけでは接続できません。PubliclyAccessibleはパブリックIPを割り当てるかどうかの設定にすぎず、外部から到達するには次の条件も必要です。

  • DBサブネットグループのサブネットがパブリックサブネット(ルートテーブルにIGWへのデフォルトルートがある)
  • セキュリティグループのインバウンドで接続元のグローバルIPとDBポートが許可されている

また、セキュリティ上の観点から本番DBをインターネットに露出させる構成は推奨されません。0.0.0.0/0での許可は避けてください。運用上どうしても外部から接続が必要なら、踏み台EC2のSSHポートフォワード、Session Managerのポートフォワーディング、クライアントVPNのいずれかを使い、プライベートのまま接続する構成にします。

telnetやncが使えない環境で到達性を確認する方法は?

bashが使えるなら/dev/tcp擬似デバイスで代替できます(前述のtimeout 5 bash -c ...)。ほかの手段もあります。

# Python 標準ライブラリのみで TCP 接続確認
python3 -c "
import socket,sys
s=socket.create_connection(('my-db.xxxxxxxx.ap-northeast-1.rds.amazonaws.com',5432),timeout=5)
print('reachable'); s.close()
"
# Windows(PowerShell)
Test-NetConnection -ComputerName my-db.xxxxxxxx.ap-northeast-1.rds.amazonaws.com -Port 5432

クライアントツールを一切入れられない環境なら、前述のReachability Analyzerが最も確実です。設定ベースで解析するため、OSにログインする必要すらありません。VPCフローログを有効にして、該当ENIのREJECTレコードを探す方法も有効です。

# CloudWatch Logs Insights のクエリ例(フローログ)
fields @timestamp, srcAddr, dstAddr, dstPort, action
| filter dstPort = 5432 and action = "REJECT"
| sort @timestamp desc
| limit 50

REJECTが記録されていればSGかACLによる遮断、レコードが一切ない場合はパケットがそもそも到達していない、つまりルーティングの問題と判断できます。

接続タイムアウトの待ち時間はどこで変更できる?

クライアント側で指定します。復旧作業中は短くしておくと切り分けが速く進みます。

# psql(libpq): connect_timeout を秒で指定
psql "host=my-db.xxxxxxxx.ap-northeast-1.rds.amazonaws.com port=5432 dbname=appdb user=appuser connect_timeout=5"
# mysql クライアント
mysql -h my-db.xxxxxxxx.ap-northeast-1.rds.amazonaws.com -u appuser -p --connect-timeout=5
# JDBC(PostgreSQL): 接続確立とソケット読み取りを別々に指定
jdbc:postgresql://host:5432/appdb?connectTimeout=5&socketTimeout=30

注意点は、「接続確立」のタイムアウトと「クエリ応答待ち」のタイムアウトが別物であることです。socketTimeoutを短くしすぎると、長時間クエリが途中で切断されます。PostgreSQLサーバー側のidle_in_transaction_session_timeoutやstatement_timeoutは接続後のセッション管理用のパラメータで、接続タイムアウトとは無関係です。

再発防止のための設定と設計のチェックポイント

同じトラブルを繰り返さないために、設定レベルで押さえておくべき点を挙げます。

セキュリティグループのソースはSG参照で書きます。CIDR直書きはサブネット追加やスケールアウトで維持できなくなります。「DB用SGのインバウンドで、アプリ用SGからの5432を許可」という形にしておけば、アプリを増やしてもSGを付けるだけで済みます。

DBサブネットグループには全AZのプライベートサブネットを含めます。フェイルオーバー先のAZが含まれていないと、切り替えが制限されます。アプリ側のサブネットからそれら全サブネットへ経路があることも確認してください。

パブリックアクセスは原則「なし」で統一します。IaCのテンプレートでPubliclyAccessible: falseを既定にし、例外を作らない運用にします。AWS Configのルールで逸脱を検知する方法もあります。

クライアント側のDNSキャッシュTTLは短くします。フェイルオーバー時の復帰時間を左右するため、JavaならJVMのnetworkaddress.cache.ttl、コンテナなら名前解決のキャッシュ設定を見直します。

コネクションプールは必ず使い、上限を設計します。アプリインスタンス数 × プール最大数がDBのmax_connectionsを超えない設計にし、LambdaやコンテナのオートスケールがあるならRDS Proxyの併用を検討します。アイドル接続の最大寿命(max lifetime)をNATのアイドルタイムアウトより短く設定しておくと、無効なコネクションの再利用を防げます。

監視は先に仕込みます。DatabaseConnections、CPUUtilization、FreeableMemory、FreeStorageSpace、NATのErrorPortAllocationにアラームを設定し、RDSのイベントサブスクリプションでフェイルオーバーやメンテナンスの通知を受け取れるようにすると、間欠障害の原因特定が速く進みます。

接続確認用の疎通スクリプトも用意しておきます。障害時に手順1〜4を手で辿るのは時間がかかります。エンドポイント取得からDNS解決、TCP疎通、pg_isreadyまでを一本のスクリプトにまとめておけば、一次切り分けが数十秒で終わります。

さらに学ぶには

なお、料金やサービスの制限値、各エンジンの既定パラメータは変更されることがあります。実際に設定を変更する前には、AWSの公式ドキュメントで最新の仕様を確認してください。