LASSIC Media らしくメディア

2026.10.02 らしくコラム

3ウェイハンドシェイクの流れ、SYN・ACKと接続失敗の原因




監修・編集責任者:牛尾 昭昌(株式会社LASSIC 執行役員)

この記事の結論

  • 3ウェイハンドシェイクは、SYN・SYN-ACK・ACKの3回で互いの初期シーケンス番号を確かめ合い、TCPの接続を確立する手順です。
  • 接続の失敗は、RSTが返る拒否とSYNに応答が無いタイムアウトに分かれ、それぞれ原因を調べる先が違います。
  • 接続のタイムアウトはOSの既定に任せずアプリケーション側で決め、失敗の種類をログに残しておきます。

※ 本記事は2026年10月時点の公式情報(省庁・公的機関、および製品やサービスを提供する事業者が公開している資料)に基づきます。

3ウェイハンドシェイクは、TCPで通信を始める前に、接続する側と待ち受ける側がSYN、SYN-ACK、ACKの3つのセグメントを送り合って接続を確立する手順です。Webの閲覧もデータベースへの接続も、TCPを使う通信はまずこの手順を通ります。「つながらない」という障害は、この3回のやり取りのどこで止まったかを見ると、原因を絞り込めます。

本記事では、システム開発やIT運用に携わる方に向けて、RFC 9293に沿って3ウェイハンドシェイクの流れと状態の移り変わりを整理します。接続の拒否とタイムアウトの違いはPythonで実際に試し、切断の手順、SYNフラッド、外部に委託するときの確認点までを取り上げます。

赤褐色の切り立った岩壁が左右から向かい合う渓谷を写した写真。岩壁のすき間の奥に岩の塔と針葉樹の林が見え、手前の谷底にも針葉樹が立っている。人も読める文字も写っていない

3ウェイハンドシェイクとは

RFC 9293は、3ウェイハンドシェイクを接続の確立に使う手順と位置づけています。*1 TCPはデータの1バイトごとに番号(シーケンス番号)を振るため、通信を始める前に、互いの番号の出発点(初期シーケンス番号)を知らせ合い、相手に確かめてもらう必要があります。

RFC 9293の説明では、この同期は、Aが自分の番号を送る、Bがそれを確認する、Bが自分の番号を送る、Aがそれを確認する、の4つの動きから成ります。2つ目と3つ目は1つのセグメントにまとめられるので、やり取りは3回になります。

TCPはOSI参照モデルのトランスポート層(第4層)のプロトコルです。TCPとUDPの違いは「通信プロトコルの基礎|TCP・UDPとHTTPの役割」で整理しています。mTLS(「mTLSとは|相互TLS認証の仕組みと使いどころ」)などで使うTLSのハンドシェイクは、TCPの接続ができた後に、その上で行われる別の手順です。

SYN・SYN-ACK・ACKの流れ

RFC 9293のFigure 6の例の値をそのまま使って、流れを図にしました。

3ウェイハンドシェイクの流れを上から下へ時間順に示した図。左が接続する側A、右が待ち受ける側B。最初はAがCLOSED、BがLISTEN。AがSYN(SEQ=100)を送ってSYN-SENTになり、受け取ったBはSYN-RECEIVEDになる。BがSYN,ACK(SEQ=300 ACK=101)を返し、受け取ったAはESTABLISHEDになる。AがACK(SEQ=101 ACK=301)を送り、受け取ったBもESTABLISHEDになって、ここから両側がデータを送れる。番号の値はRFC 9293のFigure 6の例。

1回目に、接続する側AがSYNを立てたセグメントで、シーケンス番号100から始めると知らせます。2回目に、待ち受ける側BがSYNとACKを立てたセグメントを返し、自分の番号300を知らせると同時に、ACK番号101で「100のSYNを受け取った、次は101を待つ」と伝えます。3回目に、AがACKだけのセグメントでBのSYNを確認し、接続が確立します。

続いてAが送るデータの番号も101のままです。SYNは番号を1つ使いますが、ACKはシーケンス番号の空間を使わないためです。パケットの記録で、SYNへのACK番号が「相手の番号+1」になるのはこのためです。

3回のやり取りが要るのは、シーケンス番号がネットワーク全体で共通の時計に結びついておらず、最初のSYNを受け取った側には、それが古いセグメントかどうかを判別する手段が無いからです。RFC 9293は、3ウェイハンドシェイクの主な目的を、古い重複した接続要求による混乱を防ぐことだとしています。*1

接続の状態と確かめ方

RFC 9293は、TCPの接続の状態を11に分けています(CLOSEDは接続が無いことを表す仮の状態)。*1 3ウェイハンドシェイクに関わるのは次の4つです。

3ウェイハンドシェイクに関わるTCPの4つの状態(RFC 9293の定義をもとに作成)
状態 意味 どちらの側か
LISTEN どこからでも接続要求が来るのを待っている 待ち受ける側
SYN-SENT 接続要求(SYN)を送り、相手からの接続要求を待っている 接続する側
SYN-RECEIVED 接続要求を受け取って自分も送り、その確認(ACK)を待っている 待ち受ける側
ESTABLISHED 接続が開いていて、受け取ったデータをアプリケーションに渡せる 両方

Linuxではssで状態を一覧でき、マニュアルは状態の指定にsyn-sentやsyn-recvを使えると記しています。*7 書式に従えば、「ss -t state syn-sent」で応答を待っている接続だけを表示できます。Windowsの「netstat -an -p tcp」では、本記事の検証で、応答の無いアドレスへ接続している最中の接続がSYN_SENTと表示されました。

接続する側にSYN-SENTが長く残るなら、相手から応答が返っていません。待ち受ける側にSYN-RECEIVEDが大量にあるなら、3回目のACKが届いていません。どちらの側がどの状態で止まっているかが、切り分けの出発点です。

ハンドシェイクを終えた接続は、アプリケーションがaccept()で取り出すまでキューに並びます。Linuxのlisten(2)によると、backlog引数はこの確立済みの接続のキューの長さで、確立前の要求の上限はtcp_max_syn_backlogが決めます。キューが満ちると、接続する側はECONNREFUSEDを受け取るか、要求が無視されて再送の後につながります。*5

接続失敗の2つの型

3ウェイハンドシェイクの失敗は、拒否とタイムアウトの2つの型に分かれます。

接続の失敗の2つの型と調べる先
型 ネットワークで起きていること Linux / Windows のエラー まず調べる先
拒否 SYNに対してRSTが返る ECONNREFUSED / 10061(WSAECONNREFUSED) 接続先のプロセスが動いているか、ポート番号が合っているか
タイムアウト SYNに何も返らず、再送の末に諦める ETIMEDOUT / 10060(WSAETIMEDOUT) 経路のファイアウォール、相手のホストが動いているか、経路の障害

1つ目は拒否です。接続が存在しないポートにSYNが届くと、受け取った側はRST(リセット)を返します。Linuxのconnect()はECONNREFUSEDを返し*6、Windowsでは10061になります。Microsoftは、接続先でサーバーのアプリケーションが動いていないことが通常の原因だと説明しています。*8 RSTが返る以上、相手のホストまでは届いているので、調べる先は接続先のプロセスとポートです。

2つ目はタイムアウトです。SYNに応答が無いと、接続する側は再送を重ねた末に諦めます。RFC 9293は、SYNの再送を少なくとも3分続けられる上限を求めつつ、アプリケーションが早めに諦めてもよいとしています。*1 Linuxのtcp_syn_retriesは既定で6回で、およそ127秒までの再試行にあたります。*4 ファイアウォールがSYNを黙って捨てている、経路や相手のホストが止まっている、といったときの型です。

Pythonで接続の失敗を試す

次のコードは、127.0.0.1で待ち受けを作って接続し、待ち受けをやめて同じポートへもう一度接続し、最後に応答の無いアドレスへ接続します。192.0.2.1は、RFC 5737が文書用に予約しているアドレスです。*9

import socket, time
def try_connect(label, host, port, timeout):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(timeout)
    start = time.perf_counter()
    try:
        s.connect((host, port))
        result = "接続できた"
    except OSError as e:
        result = type(e).__name__
    finally:
        s.close()
    print(f"{label}: {result} {time.perf_counter() - start:.3f}秒")
server = socket.create_server(("127.0.0.1", 0))  # 空いているポートで待ち受ける
port = server.getsockname()[1]
try_connect("待ち受けあり", "127.0.0.1", port, 5)
server.close()                                   # 待ち受けをやめる
try_connect("待ち受けなし", "127.0.0.1", port, 5)
try_connect("応答なし", "192.0.2.1", 80, 3)      # 文書用に予約されたアドレス

Windows Server 2025とPython 3.12で実行した結果は、次のとおりです。

待ち受けあり: 接続できた 0.001秒
待ち受けなし: ConnectionRefusedError 2.009秒
応答なし: TimeoutError 3.001秒

待ち受けをやめたポートではConnectionRefusedError(Windowsのエラー番号10061)、応答の無いアドレスではsettimeout()で決めた3秒でTimeoutErrorになりました。拒否でも一瞬で返るとは限らず、この環境では約2秒かかりました。

settimeout()を指定しないと、待ち時間はOSが決めます。同じ環境で指定せずに試すと、約21秒でエラー10060になりました(netshの表示はInitial RTOが1000、Max SYN Retransmissionsが4)。Linuxなら既定でおよそ127秒待つので、接続のタイムアウトはアプリケーション側で明示しておきます。値の決め方は「タイムアウト設計とは|障害の連鎖を防ぐ勘所」で扱っています。

切断の手順とTIME-WAIT

切断にも手順があります。RFC 9293のFigure 12では、先に閉じるAがFINを送ってFIN-WAIT-1へ、BはACKを返してCLOSE-WAITへ移ります。Bも閉じるとFINを送ってLAST-ACKへ移り、AのACKを受けてCLOSEDになります。AはTIME-WAITで2MSL待ってから閉じます。

RFC 9293はMSL(セグメントがネットワークに残りうる時間)を2分とし、先に閉じた側がTIME-WAITに2×MSLのあいだとどまることを必須としています。*1 相手に最後のACKが届くのを待ち、前の接続の遅れたセグメントが新しい接続に紛れ込むのを防ぐためです。短い接続を大量に開いては閉じるクライアントでTIME-WAITが溜まるのは、この仕組みによります。

FINによる通常の切断のほかに、RSTを送って接続の状態をすぐに捨てる中断もあります。Windowsのエラー10054(WSAECONNRESET)は、確立済みの接続が相手に強制的に閉じられたことを表します。*8

SYNフラッドとTCP Fast Open

待ち受ける側はSYNを受け取ると、SYN-RECEIVEDの状態をしばらく保ちます。RFC 4987は、偽の送信元からSYNを大量に送って確立前の接続のバックログを埋め、正規の接続要求を断らせる攻撃をSYNフラッドと呼び、フィルタリング、バックログの拡大、SYN cookieなどの対策を挙げています。SYN cookieは、状態を持たずに必要な情報をSYN-ACKのシーケンス番号に埋め込む方法です。*2

Linuxのtcp_syncookiesは既定で1で、SYNのバックログがあふれたときだけSYN cookieを送ります。ただしtcp(7)は、TCPの仕様に反し拡張と衝突しうるとして、高負荷のサーバーの調整手段としては勧めていません。*4 つながりにくいとき、この設定だけで片づけないほうが無難です。

RFC 7413のTCP Fast Openは、SYNとSYN-ACKにデータを載せ、3ウェイハンドシェイクの完了を待つ標準のTCPより、1往復ぶんまで早くします。ただしSYNのデータがまれにアプリケーションへ二重に渡ることがあり、それを許容できるアプリケーション以外は使うべきでないとされています。RFC 7413は実験的(Experimental)な仕様です。*3

外部に委託するときに確認しておきたい点

接続の不具合は、アプリケーションとネットワークの境目で起きるため、担当が曖昧になりがちです。委託するときは、接続のタイムアウトと再試行を、アプリケーション、OSのカーネル設定、ロードバランサのどこで決めるのかを設計書に書いてもらいます。

障害時に拒否とタイムアウトを区別してログに残すか、ssやnetstatで状態を確かめる手順が運用手順書にあるかも確認します。まとめて「接続エラー」とだけ記録していると、プロセスの停止とファイアウォールの設定漏れを見分けられません。

バックログなどのカーネルの設定を変えるなら、本番の前に検証環境で負荷をかけ、接続の成功率と待ち時間を測る計画があるかを見ておきます。

まとめ:ハンドシェイクで確かめたい3つの点

3ウェイハンドシェイクについて、実務で確かめておきたい点は3つです。第一に、SYN、SYN-ACK、ACKの3回で互いの初期シーケンス番号を確かめ合い、両側がESTABLISHEDになってからデータを送ること。第二に、失敗にはRSTが返る拒否とSYNに応答が無いタイムアウトがあり、前者は接続先のプロセスとポート、後者は経路とファイアウォールを調べること。第三に、接続のタイムアウトをアプリケーション側で決め、失敗の種類をログに残すことです。

LASSICに相談するメリット

LASSICは、元請(プライムベンダー)としてシステムの開発と保守・運用を受託しています。TCPの接続まわりでは、Linuxのカーネル設定(tcp_syn_retries、tcp_max_syn_backlog、tcp_syncookies)、ssやnetstatによる状態の確認、ロードバランサやセキュリティグループの設定を扱います。設計では、接続のタイムアウトと再試行をアプリケーション・OS・ロードバランサのどこで持つか、SYNフラッドへの備えをどの層に置くかを発注者と決めます。検証と運用では、pytestで拒否とタイムアウトの両方を再現するテストをGitHub ActionsのCI/CDで回し、PrometheusとGrafanaで接続の失敗数を監視し、サーバーの構成はTerraformとAnsibleで管理します。

よくある質問

3ウェイハンドシェイクは、リクエストのたびに行われますか

TCPの接続を新しく開くたびに行われます。同じ接続を開いたまま使い回す場合、その接続が閉じられるまで再び行う必要はありません。接続を開き直す回数が多い処理では、そのたびにハンドシェイクの往復が加わります。

SYN-ACKが返ってこないときは、どこを調べればよいですか

接続する側でSYN-SENT(WindowsのnetstatではSYN_SENT)のまま止まっているなら、SYNが相手に届いていないか、応答が途中で捨てられています。経路のファイアウォールやアクセス制御の設定、相手のホストが動いているかを確かめます。すぐに拒否される場合は、相手のプロセスとポートを確かめます。

TCP Fast Openは有効にしたほうがよいですか

SYNに載せたデータがまれに二重に届いても問題にならない処理でなければ、使わないほうが無難です。RFC 7413も、それを許容できないアプリケーションには使うべきでないとしています。有効にする場合は、同じ要求を2回受けても結果が変わらない作りかどうかを先に確かめます。

TCP接続の設計や障害調査のご相談

元請(プライムベンダー)として、接続のタイムアウト設計から障害の切り分け、システムの保守・運用までご提案します。

Remoguとリラシクなら、サーバーやネットワークの設計・運用に加わるITエンジニアも探せます。

Remoguは、リモート前提で全国から即戦力のITプロ人材を調達するサービスです。リラシクは、扱う求人がすべてリモートワークのITエンジニア専門転職エージェントです。どちらもLASSICが運営しています。

無料相談はこちら

出典

  1. *1 参考:IETF「RFC 9293: Transmission Control Protocol (TCP)」(https://www.rfc-editor.org/rfc/rfc9293)。出典:3.3.2 State Machine Overview、3.4.1 Initial Sequence Number Selection、3.5 Establishing a Connection(Figure 6)、3.5.2 Reset Generation、3.6 Closing a Connection(Figure 12)、3.8.3 TCP Connection Failures を参照(2026年10月確認)
  2. *2 参考:IETF「RFC 4987: TCP SYN Flooding Attacks and Common Mitigations」(https://www.rfc-editor.org/rfc/rfc4987)。出典:1 Introduction、2.2 Theory of Operation、3 Common Defenses(3.6 SYN Cookies)を参照(2026年10月確認)
  3. *3 参考:IETF「RFC 7413: TCP Fast Open」(https://www.rfc-editor.org/rfc/rfc7413)。出典:Abstract(1往復までの短縮、SYNのデータが再送される可能性と適用の条件)、Status of This Memo(Experimental)を参照(2026年10月確認)
  4. *4 参考:Linux man-pages「tcp(7)」(man7.org)(https://man7.org/linux/man-pages/man7/tcp.7.html)。出典:/proc interfaces の tcp_syn_retries、tcp_syncookies、tcp_max_syn_backlog を参照(2026年10月確認)
  5. *5 参考:Linux man-pages「listen(2)」(man7.org)(https://man7.org/linux/man-pages/man2/listen.2.html)。出典:DESCRIPTION(キューが満ちたときの挙動)、NOTES(backlog引数の意味と tcp_max_syn_backlog)を参照(2026年10月確認)
  6. *6 参考:Linux man-pages「connect(2)」(man7.org)(https://man7.org/linux/man-pages/man2/connect.2.html)。出典:ERRORS の ECONNREFUSED、ETIMEDOUT を参照(2026年10月確認)
  7. *7 参考:Linux man-pages「ss(8)」(man7.org)(https://man7.org/linux/man-pages/man8/ss.8.html)。出典:STATE-FILTER(syn-sent、syn-recv などの状態の指定)を参照(2026年10月確認)
  8. *8 参考:Microsoft Learn「Windows Sockets Error Codes」(https://learn.microsoft.com/en-us/windows/win32/winsock/windows-sockets-error-codes-2)。出典:WSAECONNRESET(10054)、WSAETIMEDOUT(10060)、WSAECONNREFUSED(10061)の説明を参照(2026年10月確認)
  9. *9 参考:IETF「RFC 5737: IPv4 Address Blocks Reserved for Documentation」(https://www.rfc-editor.org/rfc/rfc5737)。出典:192.0.2.0/24(TEST-NET-1)が文書用に予約されていることを参照(2026年10月確認)




View