SSD Nodes Learn 🎉 VPS $5.50/月〜
ガイド Matt Connor著者 Matt Connor ・更新日 2026-08-09

HTTPとは?サーバー管理者向けに仕組みを解説

HTTPの基本をサーバー管理者向けに解説します。メソッド、ステータスコード、重要なヘッダー、nginxのログ、HTTP/3とTLSの関係を確認できます。

HTTP とは何か

HTTP(hypertext transfer protocol)は、クライアントと Web サーバーが要求を送り、結果を返すために使用する一連の規則です。クライアントは、GET のようなメソッド、/pricing のようなパス、プロトコルのバージョン、ヘッダーの一覧、場合によってはボディを含むリクエストを送信します。サーバーは、200 のようなステータスコードに続けて、自身のヘッダーと通常はボディを返します。サーバー上でのすべてのページ表示と API(application programming interface)呼び出しは、このやり取りを繰り返したものです。

HTTP 自体は状態を保持しません。サーバーは 1 秒前に何を要求されたかを記憶していないため、ログインセッションのように状態を保持する情報は、すべてのリクエストのヘッダーに含めて送信します。この性質により、以降の多くの動作を説明できます。キャッシュは完全にヘッダーによって制御され、ロードバランサーは次のリクエストを別のバックエンドに送っても問題ありません。

以下では、このモデルがサーバー側でどのように見えるかを、アクセスログと nginx の設定を通して説明します。

注釈付きの生のリクエストとレスポンス

以下は完全な HTTP/1.1 リクエストです。空行でヘッダーが終わり、その行より後ろがボディです。GETには通常、ボディがありません。

GET /pricing HTTP/1.1
Host: example.com
User-Agent: curl/8.5.0
Accept: */*
Accept-Encoding: gzip
  • GETはメソッドで、実行する処理を指定します。GETは読み取り、POSTはデータの送信、PUTは置換、DELETEは削除を行います。HEADはボディなしでGETのヘッダーを要求します。
  • /pricingはパスです。ホスト名はリクエスト行に含まれません。そのため、次のヘッダーが存在します。
  • HTTP/1.1はクライアントが使用するプロトコルのバージョンです。
  • Host: example.comはクライアントが要求するサイトを指定します。HTTP/1.1では必須のため、nginxはこれがないリクエストに400 Bad Requestで応答します。
  • 残りは設定です。Accept-Encoding: gzipはクライアントが展開できることを示すため、サーバーはボディを圧縮できます。

レスポンスも同じ構造ですが、先頭にステータス行があります。

HTTP/1.1 200 OK
Date: Thu, 06 Aug 2026 09:12:44 GMT
Server: nginx
Content-Type: text/html; charset=utf-8
Content-Length: 5310
Cache-Control: public, max-age=300

<!doctype html>...
  • 200 OKは理由フレーズ付きのステータスコードです。重要なのはコードです。理由フレーズは装飾にすぎず、クライアントは無視します。
  • Content-Typeは、後続のバイトをクライアントがどのように扱うかを指定します。
  • Content-Lengthはボディサイズをバイト単位で示すため、クライアントはボディの終端を判断できます。サイズを事前に把握できない場合、サーバーは代わりにTransfer-Encoding: chunkedを送信し、長さ0のチャンクで終端を示します。
  • Cache-Controlは、ブラウザーと途中のキャッシュがこのレスポンスを保持できる期間を指定します。
  • ヘッダーの後の空行は、リクエストとレスポンスのどちらでも、ヘッダーとボディを区切ります。

ヘッダー名では大文字と小文字を区別しません。また、各行は単独の改行ではなく、キャリッジリターンに続くラインフィードで終わります。これらを手入力することはありませんが、パケットキャプチャーで確認することになります。

実際のリクエストとレスポンスの組を確認するには、自分が管理するサイトに対して次を実行します。

curl -sS -o /dev/null -D - https://example.com/

-D -はレスポンスヘッダーを端末に出力し、-o /dev/nullはボディを破棄します。curl -Iよりもこちらを使用してください。-IHEADリクエストを送信するためです。HEADGETを異なる方法で処理するアプリケーションサーバーは多く、その場合、ブラウザーが受信しないヘッダーが表示されます。curl -vは両方向を出力し、リクエスト行には>、レスポンス行には<の印を付けます。

nginx のアクセスログに記録されるリクエスト行

nginx には combined ログ形式が用意されており、定義は次のとおりです。

log_format combined '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent"';

この形式で出力される1行の例です。

203.0.113.45 - - [06/Aug/2026:09:12:44 +0000] "GET /pricing HTTP/1.1" 200 5310 "https://example.com/" "Mozilla/5.0 (X11; Linux x86_64) Chrome/127.0.0.0 Safari/537.36"
  • 203.0.113.45$remote_addr です。TCP (transmission control protocol) 接続を開始したアドレスを示します。プロキシの背後では、訪問者ではなくプロキシのアドレスになります。
  • 最初の - は固定のプレースホルダーです。2つ目は $remote_user で、HTTP basic authentication を使用している場合だけ値が入ります。
  • "GET /pricing HTTP/1.1"$request です。受信したままのリクエスト行がそのまま記録されます。
  • 200 はサーバーが返したステータスです。訪問者が認識したステータスではありません。
  • 5310$body_bytes_sent です。ボディだけが対象で、レスポンスヘッダーは含まれません。そのため、この値は実際に送信されたバイト数より常に小さくなります。
  • 最後の2つの引用符付きフィールドは RefererUser-Agent です。どちらもクライアントから送られるため、任意の内容を含む可能性があります。

$request は受信した内容をそのまま記録するため、不正なデータもそのまま表示されます。TLS (transport layer security) を使用するクライアントが plaintext の port 80 に接続すると、リクエストフィールドが "\x16\x03\x01\x02\x00\x01" のようなエスケープ済みバイトで始まる 400 行が記録されます。\x16 は TLS handshake のレコードタイプです。したがって、これらのバイトは ClientHello の先頭であり、リクエスト行ではありません。サーバーは正しく動作しています。何らかの設定が HTTPS を HTTP port に向けています。

ログ形式に $server_protocol も追加してください。HTTP/1.1HTTP/2.0、または HTTP/3.0 が出力されます。プロトコルの変更が実際に反映されたことを確認する最も簡単な方法です。

自分のサイトが返す一般的なステータスコードの意味

最初の数字はクラスを示します。最初に確認すべきなのは、このクラスです。

2xx は処理が成功したことを示します。 通常の読み取りには 200 OK を使用します。何かを作成した POST の後には 201 Created を使用します。返す内容がない成功には 204 No Content を使用します。これは通常、DELETE への応答です。

3xx は別の場所を参照する必要があることを示します。 301 は恒久的なリダイレクトです。ブラウザーはこれを強くキャッシュし、ユーザーがプロファイルを削除するまで残ることもあります。そのため、誤ったホスト名を指す 301 は元に戻すのが困難です。リダイレクトをまだテスト中の場合は 302 を使用します。304 Not Modified はエラーではなく成功です。クライアントが、まだ認識している ETag(エンティティタグ)を含む If-None-Match を送信したため、ヘッダーだけを返して本文を返していません。ログに 304 が多数記録されている場合、キャッシュは機能しています。

4xx はリクエストに問題があることを示します。 400 Bad Request は入力形式が不正です。401 Unauthorized は実際には認証されていないことを意味し、方式名を示す WWW-Authenticate ヘッダーを含める必要があります。403 Forbidden はリクエストを理解したものの、それでも拒否したことを示します。404 Not Found は存在しないパスです。405 Method Not Allowed はパスは正しいもののメソッドが正しくないことを示します。静的ファイルの場所に対する POST が返すのはこのステータスです。413 は、nginx の client_max_body_size を超える本文です。デフォルト値は 1 megabyte で、エラーログには client intended to send too large body と記録されます。

静的ファイルに対する 403 は、ほぼ必ず HTTP ルールではなくファイルシステムが原因です。設定を変更する前に /var/log/nginx/error.log を確認してください。open() "/srv/site/index.html" failed (13: Permission denied) は nginx のワーカーユーザーがファイルを読み取れないことを意味します。多くの場合、親ディレクトリに others 向けの実行権限がありません。directory index of "/srv/site/" is forbidden は、autoindex が無効な状態で、パスがインデックスファイルのないディレクトリに解決されたことを意味します。

5xx は自分の側で問題が発生したことを示します。 500 は、アプリケーションで処理されなかったエラーです。502 Bad Gateway は、nginx が upstream から使用可能な応答を取得できなかったことを意味します。エラーログには原因が記録されます。connect() failed (111: Connection refused) while connecting to upstream は、proxy_pass に指定されたアドレスで何も待ち受けていないことを意味します。504 Gateway Timeout は、upstream が接続を受け付けた後、proxy_read_timeout(デフォルトでは 60 seconds)以内に何も応答しなかったことを意味します。ログには upstream timed out (110: Connection timed out) while reading response header from upstream と記録されます。503 Service Unavailable は意図的な拒否です。nginx 自身のレート制限は 503 を返します。limit_req_status のデフォルト値が 503 だからです。ログで 429 Too Many Requests を探しているのに 503 が見つかる場合は、これが理由です。正確なコードを取得するには limit_req_status 429; を設定してください。

サーバーの実行時に重要なヘッダー

Host はサイトを選択します。1 つの IP アドレスで数百のホスト名を提供でき、nginx は Hostserver_name を照合して、どの server ブロックが応答するかを決定します。一致するものがない場合、nginx はデフォルトサーバーを使用します。別のサーバーに default_server が付いていない限り、デフォルトサーバーはそのアドレスとポートで待ち受ける最初のブロックです。新しい virtual host で誤ったサイトが返る場合、ほぼ必ずこれが原因です。名前が一致せず、リクエストがデフォルトサーバーにフォールスルーしています。DNS に触れずにテストできます。

curl -sS -o /dev/null -D - -H 'Host: app.example.com' http://127.0.0.1/

User-Agent はクライアントが記述する自己紹介であり、自由形式のテキストです。ログを読む際の手掛かりとして使用してください。制御には使用しないでください。偽装したいクライアントは簡単に偽装できるため、User-Agent によるスクレーパーのブロックでは、礼儀正しいクライアントだけが除外されます。

Content-Type はバイト列の解釈方法を決定します。API リクエストには application/json、ページには text/html; charset=utf-8 を使用します。nginx は /etc/nginx/mime.types でファイル拡張子をタイプに対応付けます。パッケージに含まれる nginx.confdefault_type application/octet-stream; を設定するため、nginx が認識しない拡張子のファイルは、レンダリングされずダウンロードとして提供されます。目に見える症状は、ページがスタイルなしで読み込まれ、ブラウザーのコンソールに Refused to apply style from ... because its MIME type ('text/plain') is not a supported stylesheet MIME type と表示されることです。ここでの MIME は multipurpose internet mail extensions の略で、これらのタイプ文字列の由来となった命名方式です。

Cache-Control は、サーバーと利用者の間にあるすべてのキャッシュを制御するためのヘッダーです。public, max-age=31536000, immutable は、ファイル名にコンテンツのハッシュを含むアセットに適しています。コンテンツが変わるとファイル名も変わるためです。no-store はユーザー固有の情報に付けます。ログイン済みページを共有キャッシュが保持すると、同じ URL を要求した次の利用者にそのページが渡るためです。private は中間的な設定です。ブラウザーは保持できますが、共有キャッシュは保持できません。

X-Forwarded-For は、プロキシによって訪問者のアドレスが隠されるために存在します。リクエストが reverse proxy を通過すると、$remote_addr はプロキシのアドレスになります。そのため、ログ、地理位置情報、rate limiting のすべてで、クライアントが 1 つに見えます。プロキシは元のアドレスを引き渡す必要があります。

proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

受信側のサーバーには、そのアドレスを信頼するように設定し、どの相手を信頼するかも明確に指定する必要があります。

set_real_ip_from 10.0.0.0/8;
real_ip_header X-Forwarded-For;

自分で管理している範囲だけを列挙してください。X-Forwarded-For は任意のクライアントが送信できる平文です。そのため、set_real_ip_from 0.0.0.0/0; を設定しないと、訪問者がログに記録されるアドレスと rate limiter が数えるアドレスを選べてしまいます。

X-Forwarded-Proto は、特定の非常によくある障害を防ぎます。プロキシが TLS を終端し、リクエストを平文の HTTP でアプリケーションに転送する構成を考えます。アプリケーションは平文のリクエストを受け取るため、訪問者は HTTPS を使用すべきだと判断し、301 https://example.com/ を返します。ブラウザーはそれに従い、プロキシは再び TLS を終端して、平文の HTTP で転送します。この処理が繰り返され、最終的にブラウザーは ERR_TOO_MANY_REDIRECTS で処理を中止します。X-Forwarded-Proto: https を送信すると、アプリケーションに訪問者がすでに HTTPS を使用していることが伝わるため、リダイレクトが停止します。

HTTP/1.1、HTTP/2、HTTP/3 の違いと実際の影響

HTTP/1.1 はテキスト形式で、1 つの接続につき一度に 1 つのリクエストを処理します。Connection: keep-alive を使うと、次のリクエストで同じ TCP 接続を再利用できるため、接続確立のコストを抑えられます。ただし、レスポンスはリクエストした順序で返されます。遅いレスポンスが 1 つあると、その後ろで待機しているすべての処理が止まります。これはヘッドオブラインブロッキングと呼ばれます。ブラウザーは通常、同じホスト名に対して複数の接続を同時に開くことで回避します。

HTTP/2 は同じメソッドとステータスコードを維持し、フレーミングをバイナリ形式に変更します。複数のリクエストが、独立したストリームとして 1 つの接続を共有します。繰り返し使用されるヘッダーテキストも圧縮されます。現在のリクエストには多くのヘッダーが含まれるため、これは重要です。接続自体は TCP のままなので、パケットを 1 つ失うと、再送パケットが到着するまで、その接続上のすべてのストリームが停止します。ヘッドオブラインブロッキングはなくなっていません。HTTP からトランスポート層へ移動しただけです。Server Push は HTTP/2 の機能でしたが、Chrome が 2022 年にサポートを削除したため、実際には廃止されています。

HTTP/3 も同じセマンティクスを維持し、TCP の代わりに QUIC を使用します。QUIC は UDP(user datagram protocol)上に構築されたトランスポートです。QUIC のストリームは下位層まで独立しているため、パケットを失っても、そのパケットが属していたストリームだけが停止します。TLS 1.3 は QUIC のハンドシェイクに組み込まれており、その上に別途重ねる必要がありません。そのため、新しい接続に必要なラウンドトリップが少なくなります。実際には 2 つの点に注意が必要です。経路上のすべてのファイアウォールで UDP 443 を開く必要があります。また、UDP を制限またはブロックするネットワークでは、クライアントは HTTP/2 に戻ります。

では、具体的に何が変わるのでしょうか。ブラウザーは HTTP/3 から接続を開始することはありません。まず HTTP/2 または HTTP/1.1 で接続し、レスポンスに含まれる Alt-Svc: h3=":443"; ma=86400 ヘッダーを確認してから、そのホストへの後続の接続で HTTP/3 を使用します。したがって、このヘッダーは単なる付加情報ではありません。HTTP/3 を発見するための仕組みです。nginx では、バージョン 1.25.1 で HTTP/2 が独立したディレクティブになりました。server ブロック内の http2 on; が、以前の listen ... http2 パラメーターに置き換わります。QUIC は mainline 1.25.0 で導入され、HTTP/3 サイトでは通常の listen 443 ssl; と併せて listen 443 quic reuseport; が必要です。

ここではプロキシごとの成熟度が異なるため、実際に運用しているバージョンで確認する必要があります。2026 年 8 月時点で、Caddy は設定なしで HTTP/3 をデフォルトで提供します。nginx では、上記の Alt-Svc ヘッダーに加えて、明示的な quic リスナーが必要です。Traefik では、明示的な http3 オプションをエントリーポイントごとに設定します。複数の Docker アプリの前段に Traefik を置く構成で TLS を終端する場合、訪問者が使用するプロトコルのバージョンは Traefik で決まります。プロキシからコンテナまでの区間は、ブラウザーがネゴシエートしたプロトコルに関係なく、通常は平文の HTTP/1.1 です。

推測せずに検証してください。curl --http3 -sS -o /dev/null -D - https://example.com/ が機能するのは、curl -V の機能一覧に HTTP3 が含まれている場合だけです。多くのディストリビューションのビルドには、これが含まれていません。確実な確認方法は、自分のログを確認することです。フォーマットに $server_protocol を追加し、実際のブラウザーがネゴシエートした内容を読み取ります。その前に、UDP 443 が実際に開いていることを確認してください。TCP 443 だけを許可するファイアウォールでは、サイトが HTTP/2 で動作し続ける一方、HTTP/3 は静かに失敗します。Linux サーバーで開いて待ち受けているポートを確認する方法を把握することが、最初に確認すべき事項です。

HTTPS: HTTP はプロトコル、TLS はラッパーです

HTTPS は独立したプロトコルではありません。HTTP と同じリクエストとステータスコードを、TLS セッション内で送信するものです。ポート 80 では平文で送信し、ポート 443 では暗号化して送信します。最初に TLS ハンドシェイクが完了し、その後、暗号化されたチャネル内を HTTP リクエストが通ります。この順序のため、証明書の問題にステータスコードが付くことはありません。HTTP のバイトが 1 つも送信される前に失敗するため、番号を持つレスポンスが存在しないからです。

複数のサイトをホストするサーバーでは、1 つの順序が重要です。証明書は SNI(server name indication)を使用して選択されます。SNI は TLS ハンドシェイク内のフィールドで、HTTP ヘッダーが存在する前にホスト名を平文で伝えます。つまり、サーバーは最初に SNI から証明書を選び、次に Host ヘッダーから仮想ホストを選びます。これは別々の 2 つの検索で、通常は同じ結果になります。一致しない場合、ブラウザーは NET::ERR_CERT_COMMON_NAME_INVALID のような名前不一致を表示し、リクエストを一切送信しません。これは、デフォルトサーバーの証明書が、その証明書の対象外である名前に対して提示されたためです。

公開サイトでは、正式な証明書を取得し、自動更新させてください。nginx で Let's Encrypt を使用する Certbot は、サーバーブロックに証明書のパスを書き込み、更新タイマーも設定します。内部名や自ネットワーク上の単独の IP アドレスなど、公開認証局が検証できないホスト名には、Ubuntu で自己署名証明書を作成する方法が適切です。ただし、すべてのクライアントにその証明書を信頼するよう設定する必要があります。

TLS が機能したら、ポート 80 のすべての通信をポート 443 に転送します。

server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri;
}

確実な場合にだけ Strict-Transport-Security を追加してください。add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always; ヘッダーは、そのホスト名に対して平文の HTTP を 2 年間拒否するようブラウザーに指示します。ブラウザーは自身のキャッシュに基づいてこの指示に従うため、後からヘッダーを削除しても取り消せません。最初は max-age を数時間に設定し、すべてのサブドメインが本当に HTTPS を使用していることを確認してから、期間を延ばしてください。

FAQ

HTTP と HTTPS の違いは何ですか?

HTTPS は、TLS (transport layer security) セッション内で送信される HTTP です。メソッドとステータスコードは同じです。異なるのは、クライアントと TLS 終端の間でバイト列が暗号化されることと、デフォルトのポートが 80 から 443 に変わることです。TLS ハンドシェイクは最初の HTTP バイトが送信される前に完了するため、証明書の失敗でステータスコードが返ることはありません。そのため、ブラウザーの証明書警告には 403 のような番号ではなく、NET::ERR_CERT_COMMON_NAME_INVALID のようなエラー名が表示されます。

サイトが 502 Bad Gateway を返すのはなぜですか?

nginx の 502 は、nginx がプロキシ先の upstream から有効な応答を取得できなかったことを示します。つまり、訪問者のリクエストは正常で、nginx の背後に問題があります。/var/log/nginx/error.log を確認してください。connect() failed (111: Connection refused) while connecting to upstream は、proxy_pass に示されたアドレスとポートで待ち受けているプロセスがないことを示します。アプリケーションが実行中で、想定したアドレスに bind されているか確認してください。no live upstreams while connecting to upstream は、繰り返し失敗した結果、upstream ブロック内のすべてのサーバーが停止状態として扱われていることを示します。504 Gateway Timeout と比較してください。これは upstream が接続を受け付けた後、proxy_read_timeout 以内に応答できなかったことを示します。

access log にすべての訪問者について同じ IP アドレスが表示されるのはなぜですか?

$remote_addr は TCP 接続を開いたアドレスを記録するためです。リバースプロキシまたはコンテンツ配信ネットワークの背後では、そのアドレスはプロキシになります。訪問者のアドレスは代わりに X-Forwarded-For ヘッダーで渡されます。プロキシで proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; を設定し、受信側の nginx では set_real_ip_from にプロキシのアドレス範囲を設定して real_ip_header X-Forwarded-For; します。自分で管理する範囲だけを指定してください。このヘッダーは任意のクライアントが送信できるテキストです。そのため、インターネット全体からの値を信頼すると、訪問者がログに記録するアドレスとレート制限の対象にするアドレスを選べてしまいます。

HTTP/2 または HTTP/3 を有効にする必要はありますか?

すでに TLS を使用しているサイトであれば、HTTP/2 は有効にする価値があります。1 つのディレクティブで設定でき、多数の小さなファイルを含むページを遅くする接続ごとのリクエスト制限をなくせるためです。HTTP/3 の効果はより小さく確実性も低く、UDP ポート 443 の開放と、QUIC 対応のプロキシビルドが必要です。ブラウザーが HTTP/3 に切り替えるのは、以前の応答で Alt-Svc ヘッダーを受信した後だけです。そのため、そのヘッダーがなければ listen 行の内容に関係なく何も変わりません。ログ形式に $server_protocol を追加し、時間をかける前に訪問者が実際にどのプロトコルをネゴシエートしているか測定してください。

ファイルが存在するのに 403 Forbidden になるのはなぜですか?

静的サイトでは、403 は通常、HTTP ルールではなくファイルシステムの権限に関するエラーです。/var/log/nginx/error.log 内の open() ... failed (13: Permission denied) は、nginx の worker ユーザーがファイルを読み取れないことを示します。多くの場合、ファイル自体のモードではなく、親ディレクトリに others 向けの execute 権限がないことが原因です。directory index of ... is forbidden は、リクエストが index ファイルのないディレクトリに解決され、autoindex が無効になっていることを示します。一致する location ブロック内に明示的な deny ルールがある場合も 403 を返します。そのため、error log に何も記録されていない場合は、そのブロックを確認してください。

#http#https#web-server#headers#http3