ss -tulpn の出力を1列ずつ解剖

繋がらない原因がサーバー側かどうかを自力で切り分けられる。ss -tulpnの出力を列ごとに解剖し、0.0.0.0と127.0.0.1の差、Recv-Qが溜まる意味、CLOSE_WAITの山が示す原因まで読み解ける。

応用コマンド出力LinuxネットワークTCP最終更新: 2026-07-28
3つの要点
TL;DR
  1. ss -tulpnはTCP・UDPの待受ソケットを、所有プロセス付き・名前解決なしで示す。-l指定ではESTABやTIME-WAITは出ないため、確認時は-lを外し-tanpなどを使う。
  2. Recv-QとSend-Qは状態で意味が変わる。LISTEN時はaccept待ち数とバックログ上限で、一致すると新規接続を捨てる。ESTAB時はアプリ未読バイトと相手未確認バイトだ。
  3. CLOSE-WAITの累積はFIN受信後にアプリがcloseしていない証拠で、原因は自分のコードにある。TIME-WAITは先にcloseした側が約60秒保持する正常な後始末だ。

このコマンドは何を見せているか

ssはsocket statisticsの略で、カーネルが握っているソケットの一覧を見せるコマンドだ。/proc/net/tcp を舐めていたnetstatと違い、netlink経由でカーネルのsock_diagへ直接問い合わせるぶん速い。ss -tulpn はその中から「いま何がどこで待ち受けているか」だけを取り出す定型句で、5文字のオプションはそれぞれ独立している。

文字正式名効果
-t--tcpTCPソケットを対象に加える
-u--udpUDPソケットを対象に加える
-l--listening待ち受け中のソケットだけに絞る
-p--processesソケットを持つプロセス名とPIDを添える
-n--numericサービス名を解決せずポート番号のまま出す

-t と -u は対象を足し算する指定で、両方書けばTCPとUDPが混ざって出る。-n がないとssは /etc/services を引いて 22 を ssh、80 を http と書き換えるので、番号のまま読みたければ常に付ける。ポート番号と用途はポート・シグナル・errno早見表にまとめてある。

最も強く効いているのは -l だ。待ち受け中のソケットだけを残すフィルタであり、裏を返せば ss -tulpn の出力に ESTAB や TIME-WAIT が現れることは絶対にない。

-p は sudo とセットで覚える

他ユーザーのソケットのプロセスを見るには特権が要る。sudoなしだとProcess列が空欄になり、「-p を付けたのにプロセスが分からない」と誤解しやすい。

横にスクロール

ssの待受アドレスとキューから接続障害の場所を切り分ける手順
待受の有無だけでなく、束縛アドレス・所有PID・接続元からの実通信を対応付ける。

1列ずつ解剖

題材は、nginxとPostgreSQLとJavaアプリが同居するLinuxサーバーの出力だ。

$ sudo ss -tulpn
Netid State  Recv-Q Send-Q  Local Address:Port  Peer Address:Port  Process
udp   UNCONN 0      0       127.0.0.53%lo:53    0.0.0.0:*          users:(("systemd-resolve",pid=612,fd=13))
udp   UNCONN 0      0       0.0.0.0:123         0.0.0.0:*          users:(("chronyd",pid=778,fd=5))
tcp   LISTEN 0      4096    127.0.0.1:5432      0.0.0.0:*          users:(("postgres",pid=1102,fd=5))
tcp   LISTEN 128    511     0.0.0.0:80          0.0.0.0:*          users:(("nginx",pid=1235,fd=6),("nginx",pid=1234,fd=6))
tcp   LISTEN 0      128     0.0.0.0:22          0.0.0.0:*          users:(("sshd",pid=901,fd=3))
tcp   LISTEN 0      128     [::]:22             [::]:*             users:(("sshd",pid=901,fd=4))
tcp   LISTEN 0      4096    [::]:8080           [::]:*             users:(("java",pid=1580,fd=42))

Netid — ソケットの種類

先頭の列はソケットの系統だ。この列は -t と -u を同時に指定したから現れている。-t だけならすべてtcpに決まるため、ssはNetid列そのものを省く。

State — LISTENとUNCONN

2列目は状態だ。tcpの行はすべてLISTEN、つまり接続の到着を待っている。-l を付けたのだから当然である。

対してudpの行はUNCONNと出る。UDPには接続という概念がないので、待ち受けているUDPソケットは「相手が固定されていない(unconnected)」としか言いようがない。UDPの行にLISTENを探しても永遠に見つからない。-u と -l で拾われるのはこのUNCONNだ。

Recv-Q と Send-Q — Stateで意味が変わる2列

本稿で最も重要な2列だ。列名は同じなのに、State次第で意味が入れ替わる。

StateがRecv-Q の意味Send-Q の意味
LISTENacceptされるのを待っている接続の本数(現在のバックログ)バックログの上限値
LISTEN以外(ESTABなど)受信済みだがアプリがまだ読んでいないバイト数送信済みだが相手からACKが返っていないバイト数

サンプルはすべてLISTENなので前者で読む。nginxの行はRecv-Qが128、Send-Qが511。「511本まで積める待ち行列に128本の接続が溜まっていて、nginxがまだacceptで拾っていない」という意味だ。バイト数ではない。

Send-Qの511はnginxの既定バックログ値だ。この上限は、listen(2) に渡した値と net.core.somaxconn の小さいほうで決まる。Recv-QがSend-Qに達すると待ち行列は溢れ、既定では溢れた接続を黙って捨てる。クライアントからは「たまに接続だけがやたら遅い」としか見えない厄介な障害だ。

一方、-l を外して見るESTABの行では、同じRecv-Qが「カーネルは受け取ったのにアプリが read() していないバイト数」に変わる。膨らめば詰まっているのはアプリだ。Send-Qが膨らむのは、送ったデータのACKが返らない、つまり相手か経路が受け切れていない証拠だ。

同じ列名に2つの意味

Recv-Q・Send-Qを読むときは必ず先にState列を見る。LISTENなら接続の本数、それ以外ならバイト数だ。この取り違えが、バックログ溢れをバッファ溢れと誤診させる。

Local Address:Port — どこで待っているか

実務でいちばん誤解を生む列だ。3つの書き方に分けて読む。

表記意味外部ホストから繋がるか
0.0.0.0:80そのホストのすべてのIPv4アドレスで待つ繋がる(あとはファイアウォール次第)
127.0.0.1:5432ループバックのみ。同じホストの中からだけ繋がらない
[::]:8080すべてのIPv6アドレスで待つワイルドカード繋がる。多くの環境ではIPv4からも繋がる

PostgreSQLの 127.0.0.1:5432 は「このマシンの中からしか繋げない」という宣言だ。他のホストからは、ファイアウォールをどれだけ開けても届かない。パケットがそのソケットまで辿り着かないからである。nginxの 0.0.0.0:80 はその反対で、ホストが持つすべてのIPv4アドレスで待つ。

[::] はIPv6の全アドレスを表すワイルドカードで、IPv4の 0.0.0.0 に対応する。角括弧は、コロンだらけのアドレスの末尾に付く :8080 をポート番号だと読ませるためのIPv6の記法だ。

Linuxは既定で net.ipv6.bindv6only が0であり、[::] で待ち受けたソケットはIPv4からの接続もIPv4射影アドレスとして受け取る。Javaアプリが [::]:8080 の1行しか出していなくてもIPv4から繋がるのはこのためだ。0.0.0.0の行がないからIPv4で待っていない、と早合点しないこと。なお %lo はloインターフェースに縛られたソケットを示す。

Peer Address:Port — LISTENに相手はいない

接続の相手側だ。LISTENとUNCONNの行では相手がまだ存在しないので、すべて 0.0.0.0:*[::]:* になる。アスタリスクは「任意」の意味だ。ss -tulpn を読む限りこの列に情報はない。-l を外してESTABの行を見て初めて、相手のIPアドレスとポートが入る。

Process — 誰が持っているソケットか

-p が足した列だ。

users:(("nginx",pid=1235,fd=6),("nginx",pid=1234,fd=6))

プロセス名・PID・ファイルディスクリプタ番号の組が並ぶ。nginxの行に2組あるのは、マスタープロセスが開いた待ち受けソケットをfork後のワーカーが引き継いでいるからで、異常ではない。sshdの2行は逆にPIDが同じ901のままfdだけ3と4に分かれる。1プロセスが2つのソケットを開いている姿だ。

-l を外すと見える世界

流れている接続を見るには -l を外し、-a(すべてのソケット)を付ける。

$ sudo ss -tanp
State      Recv-Q Send-Q  Local Address:Port  Peer Address:Port  Process
LISTEN     0      511     0.0.0.0:80          0.0.0.0:*          users:(("nginx",pid=1235,fd=6))
ESTAB      0      0       10.0.0.5:80         203.0.113.9:51234  users:(("nginx",pid=1235,fd=12))
ESTAB      52488  0       10.0.0.5:8080       10.0.0.6:41022     users:(("java",pid=1580,fd=88))
CLOSE-WAIT 1      0       10.0.0.5:8080       10.0.0.7:39114     users:(("java",pid=1580,fd=91))
TIME-WAIT  0      0       10.0.0.5:44930      10.0.0.8:5432
SYN-SENT   0      1       10.0.0.5:52310      10.0.0.9:6379      users:(("java",pid=1580,fd=95))

状態の呼び名は、netstatに慣れた目には少しずれて見える。

ssの表示netstatの表記意味
LISTENLISTEN接続を待ち受け中。-l で残るのはこれだけ
ESTABESTABLISHED接続が確立し、データをやり取りできる状態
SYN-SENTSYN_SENTSYNを送ったが応答が返っていない
TIME-WAITTIME_WAIT先にcloseした側が約60秒保持する後始末
CLOSE-WAITCLOSE_WAIT相手のFINを受けたが自分がcloseしていない

ssはESTABLISHEDをESTABと縮め、区切りをアンダースコアではなくハイフンで書く。だから ss -tan | grep TIME_WAIT と打っても1行も引っかからない。状態で絞るならss自身のフィルタ、すなわち ss -tan state time-wait を使う。

SYN-SENTは、SYNを送ったのにSYN/ACKが返っていないことを示す。相手のポートが閉じているだけなら即座にRSTが返り、connectはすぐ失敗して行は残らない。数秒も居座るのはパケットが黙って捨てられている印で、ファイアウォールのDROPを疑う。TIME-WAITの行にProcessが無いのは、fdがすでに手放され、カーネルだけが保持しているからだ。状態遷移の全体像はネットワークで扱っている。

つまずきやすい点

第一に、127.0.0.1で待ち受けているサーバーへ外から繋ごうとしている場合。サーバー側では「プロセスも動いているしポートも開いている」と見えるのに、クライアントからは繋がらない。ss -tulpn でLocal Addressが 0.0.0.0 ではなく 127.0.0.1 だと分かった瞬間に調査は終わる。直すのはアプリの待ち受けアドレス設定であって、ネットワーク側ではない。

第二に、CLOSE-WAITの山。数百、数千と積み上がっているなら原因はほぼ確実に自分のアプリだ。CLOSE-WAITは「相手からFINを受けてACKを返し、あとは自分のアプリが close() を呼ぶのを待っている」状態を意味する。相手はすでに切断を宣言しており、応答していないのはこちらである。カーネルはこの状態を勝手に終わらせない。アプリがcloseするかプロセスが死ぬまで残り、やがてファイルディスクリプタを食い尽くして「Too many open files」に至る。Process列のPIDが犯人だ。

第三に、TIME-WAITを異常とみなすこと。これは先にcloseを呼んだ側だけが通る正常な後始末で、最後のACKが失われて相手がFINを再送したときに応答するため、そして次の接続に古いパケットが紛れ込まないために要る。Linuxでは保持時間が60秒に固定され、sysctlで縮められない。自分から大量にcloseするプロキシやAPIサーバーでTIME-WAITが数千あるのは、むしろ健全な姿だ。

TIME-WAITを消しにいかない

tcp_tw_recycle はNAT配下で接続が壊れる欠陥があり、Linux 4.12で削除された。今も残る解説を真似ても効かないどころか有害だ。tcp_tw_reuse は発信側にのみ効く別物である。

第四に、Recv-Qが減らない状況。列の意味がStateで変わる原則がそのまま効く。LISTENの行で張り付いているなら、アプリがacceptで接続を拾えていない。ワーカー数が足りないか、全員が何かで詰まっている。ESTABの行で張り付いているなら、届いたデータをアプリが read() していない。どちらも「ネットワークが遅い」ではなく「アプリが処理できていない」証拠だ。

まとめ

ss -tulpn は、-t と -u で対象を選び、-l で待ち受けだけに絞り、-p で持ち主を、-n で素の番号を出す。読むときはState列を先に見て、Recv-QとSend-Qを本数として読むのかバイト数として読むのかを決める。Local Addressの 0.0.0.0127.0.0.1[::] を区別できれば、繋がらない問題の大半はその場で切り分けられる。CLOSE-WAITの山は自分のコードを、TIME-WAITは何も語っていないことを、Recv-Qの滞留はアプリの詰まりを指す。他のコマンドの読み方はコマンド出力の読み方に揃えてある。

コマンド出力の読み方の記事ガイド

ss -tulpn の出力を1列ずつ解剖を実務で読む

TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。

解決すること

コマンド出力

比較で見る軸

難易度: advanced / カテゴリ: コマンド出力の読み方 / タグ数: 4

導入後に効く点

Recv-QとSend-Qは状態で意味が変わる。LISTEN時はaccept待ち数とバックログ上限で、一致すると新規接続を捨てる。ESTAB時はアプリ未読バイトと相手未確認バイトだ。

先に潰すリスク

用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。

数字・仕様の読み方
難易度
advanced
カテゴリ
コマンド出力の読み方
タグ数
4

判断チェックリスト

  • 自社の用途が「コマンド出力 / Linux」に近いか確認する。
  • 強みである「ss -tulpnはTCP・UDPの待受ソケットを、所有プロセス付き・名前解決なしで示す。-l指定ではESTABやTIME-WAITは出ないため、確認時は-lを外し-tanpなどを使う。」が本当に評価軸になるか確認する。
  • 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
  • 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
  • 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
  • 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。

次に確認する観点

コマンド出力LinuxネットワークTCP