こういう迷いは、AWS入門の最初の壁でもある。
VPCのサブネット設計は、AWSインフラのセキュリティと可用性を決める土台だ。ここの判断が曖昧なままだと、意図せずRDSがインターネットに露出したり、プライベートサブネット内のEC2からパッケージ更新ができなくなったりと、後から直すコストが高い問題に発展する。セキュリティグループでアクセス制御をかけていても、サブネット設計の誤りは根本的な防御の穴になる。
この記事では、VPCのパブリック・プライベートサブネット分離の設計思想を整理し、AWS CLIを使った実装手順を段階的に解説する。CIDRブロックの設計から、インターネットゲートウェイ・NATゲートウェイの配置、ルートテーブルの設定、マルチAZ展開のパターン、セキュリティグループ・Network ACLとの役割分担まで、実務判断の根拠を示しながら説明していく。
この記事のポイント
・ルートテーブルの0.0.0.0/0がIGWを向いているかで「パブリック」かが決まる
・プライベートのアウトバウンドはNATゲートウェイ(パブリックサブネット配置)で確保
・セキュリティグループはL3/4止まり——サブネット設計と組み合わせて多層防御を作る
・aws ec2 create-subnetとルートテーブル設定でCLIから段階的に構成できる
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
なぜVPCのサブネット設計が重要なのか
VPCはAWSが提供する仮想ネットワーク空間で、その内部にサブネットを切り、EC2・RDS・ElastiCacheなどのリソースを配置する。「どのサブネットにどのリソースを置くか」という判断が、そのままセキュリティアーキテクチャの境界線を決める。ここでよく誤解されるのが「セキュリティグループで絞ればサブネット設計は関係ない」という考え方だ。セキュリティグループはIPアドレスとポートの組み合わせで制御するL3/4のフィルタリングであり、強力ではあるが共通の限界がある。
・インターネット疎通の有無はルートテーブルで決まるため、SGで「全拒否」にしていてもサブネット配置が誤っていると設計の意図と実態がズレる
・外部からの直接インバウンド経路自体を断つ(プライベートサブネットへ配置する)ことと、SGで個別ポートを閉じることはまったく別の防御レイヤーだ
具体的な問題例を挙げると:
・RDSインスタンスをパブリックサブネットに置くと、インターネットからDBポート(3306等)への直接接続を試みられるリスクが生まれる
・プライベートサブネット内のEC2にNATゲートウェイへのルートがないと、yum / apt によるパッケージ更新ができず、セキュリティパッチ適用に支障をきたす
・NATゲートウェイをプライベートサブネットに誤って配置すると、インターネットへの通信経路が成立せず「pingは通るのに外には出られない」という状況になる
正しく設計されたVPCは「外に見せるもの(パブリック)」と「内部に閉じるもの(プライベート)」を明確に分け、各リソースが必要最小限の通信経路だけを持つ構成になっている。
AWSのLinux実務を体系的に学びたい場合は、AWS(Amazon Linux)の基礎から実践まで解説した入門ガイドも参照してほしい。
VPCとサブネットの基本概念
1. VPCとCIDRブロックの決め方
VPCには/16~/28のCIDRブロックを割り当てる(RFC 1918のプライベートアドレス帯を使用)。実務では10.0.0.0/16が最もよく使われる理由は、拡張性にある。/16だとIPアドレスが65,536個あり、/24のサブネット(256アドレス)を最大256個切り出せる。RDSやALBはENI(Elastic Network Interface)を複数消費するため、サブネットは/24以上の広さを推奨する。/28(16アドレス)では可用性を確保しながら稼働させるリソースの数が制限される。
CIDRを決める際の追加確認ポイント:
・オンプレミスとVPNやDirect Connectで繋ぐ予定があるか → アドレス帯が重複しないよう注意
・複数VPCをピアリングする予定があるか → 全VPCで重複しないCIDRを事前に確保する
・VPCのCIDRは後から変更できない(追加は可能)。設計段階で十分な広さを確保すること
複数AZへの展開を前提に、サブネットCIDRは「パブリックを10番台・プライベートを20番台(またはAZごとに1x/2x台)」のようにルールを統一しておくと、リソース数が増えても一目で種別が判断できる。マルチアカウント構成を見越す場合は、アカウントごとに10.0.0.0/16・10.1.0.0/16・10.2.0.0/16のように/16単位でVPCを割り当てる設計が多い。
2. パブリックサブネットとプライベートサブネット
AWSでは「パブリックサブネット」「プライベートサブネット」という区別は、コンソールのスイッチではなく、そのサブネットに関連付けたルートテーブルの内容で決まる。・ルートテーブルに「0.0.0.0/0 → インターネットゲートウェイ(IGW)」がある → パブリックサブネット
・ルートテーブルに「0.0.0.0/0 → NATゲートウェイ」がある → プライベートサブネット(アウトバウンドのみ)
・0.0.0.0/0のルートが存在しない → インターネット通信不可
この仕組みを理解していないと、「プライベートと思っていたサブネットが実はパブリックだった」という見落としが起きる。コンソールのサブネット一覧でルートテーブルを確認し、「0.0.0.0/0の向き先がIGWかNATかを常に把握すること」が運用の基本だ。
用途別の配置目安:
・パブリックサブネット: ALB(アプリケーションロードバランサー)、踏み台EC2、NATゲートウェイ
・プライベートサブネット: アプリケーションEC2、RDS、ElastiCache、ECS Fargateタスク
3. ルートテーブルの役割
ルートテーブルは「宛先IPアドレス範囲 → 転送先ゲートウェイ」を定義するテーブルだ。各サブネットに1つのルートテーブルを関連付ける。VPC作成時にメインルートテーブルが自動生成され全サブネットに適用されるが、サブネットごとに個別のルートテーブルを割り当てることで上書きできる。パブリックサブネット用ルートテーブルの構成例:
| 宛先 | ターゲット | 用途 |
|---|---|---|
| 10.0.0.0/16 | local | VPC内の通信(自動追加) |
| 0.0.0.0/0 | igw-xxxxxxxxxx | インターネット向け通信 |
| 宛先 | ターゲット | 用途 |
|---|---|---|
| 10.0.0.0/16 | local | VPC内の通信(自動追加) |
| 0.0.0.0/0 | nat-xxxxxxxxxx | アウトバウンドのみ許可 |
CLIでサブネット構成を実装する
以下の手順は Amazon Linux 2023 / Ubuntu 24.04 LTS で動作確認している。前提としてaws configure でアクセスキーと東京リージョン(ap-northeast-1)が設定済みであること。1. VPCを作成する
$ aws ec2 create-vpc \ --cidr-block 10.0.0.0/16 \ --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=prod-vpc}]' # 実行結果 { "Vpc": { "CidrBlock": "10.0.0.0/16", "State": "pending", "VpcId": "vpc-0a1b2c3d4e5f60001", "IsDefault": false } }
$ VPC_ID=vpc-0a1b2c3d4e5f60001
$ aws ec2 modify-vpc-attribute \ --vpc-id $VPC_ID \ --enable-dns-hostnames '{"Value":true}' $ aws ec2 modify-vpc-attribute \ --vpc-id $VPC_ID \ --enable-dns-support '{"Value":true}' # 成功時は何も出力されない(エラーがなければOK)
2. サブネットを作成する
パブリックサブネット(ap-northeast-1a)を作成する:$ aws ec2 create-subnet \ --vpc-id $VPC_ID \ --cidr-block 10.0.1.0/24 \ --availability-zone ap-northeast-1a \ --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=pub-subnet-1a}]' # 実行結果 { "Subnet": { "SubnetId": "subnet-0pub1a234567890ab", "VpcId": "vpc-0a1b2c3d4e5f60001", "CidrBlock": "10.0.1.0/24", "AvailabilityZone": "ap-northeast-1a", "State": "available" } }
$ aws ec2 create-subnet \ --vpc-id $VPC_ID \ --cidr-block 10.0.11.0/24 \ --availability-zone ap-northeast-1a \ --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=prv-subnet-1a}]' # 実行結果 { "Subnet": { "SubnetId": "subnet-0prv1a234567890cd", "VpcId": "vpc-0a1b2c3d4e5f60001", "CidrBlock": "10.0.11.0/24", "AvailabilityZone": "ap-northeast-1a", "State": "available" } }
3. インターネットゲートウェイを設定する
# IGWを作成する $ aws ec2 create-internet-gateway \ --tag-specifications 'ResourceType=internet-gateway,Tags=[{Key=Name,Value=prod-igw}]' # 実行結果 { "InternetGateway": { "InternetGatewayId": "igw-0abc12345def67890", "State": "available" } } # 作成したIGWをVPCにアタッチする $ aws ec2 attach-internet-gateway \ --internet-gateway-id igw-0abc12345def67890 \ --vpc-id $VPC_ID # 成功時は何も出力されない(エラーがなければOK)
4. パブリックサブネットのルートテーブルを設定する
# パブリック用ルートテーブルを作成する $ aws ec2 create-route-table \ --vpc-id $VPC_ID \ --tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=pub-rtb}]' # 実行結果 { "RouteTable": { "RouteTableId": "rtb-0pub1234567890abc", "VpcId": "vpc-0a1b2c3d4e5f60001" } } # 0.0.0.0/0 をIGWへ向けるルートを追加する $ aws ec2 create-route \ --route-table-id rtb-0pub1234567890abc \ --destination-cidr-block 0.0.0.0/0 \ --gateway-id igw-0abc12345def67890 # 実行結果 { "Return": true } # パブリックサブネットにルートテーブルを関連付ける $ aws ec2 associate-route-table \ --route-table-id rtb-0pub1234567890abc \ --subnet-id subnet-0pub1a234567890ab # 実行結果 { "AssociationId": "rtbassoc-0abc123456789def0", "AssociationState": { "State": "associated" } }
$ aws ec2 describe-route-tables \ --route-table-ids rtb-0pub1234567890abc \ --query 'RouteTables[0].Routes' # 実行結果 [ { "DestinationCidrBlock": "10.0.0.0/16", "GatewayId": "local", "State": "active" }, { "DestinationCidrBlock": "0.0.0.0/0", "GatewayId": "igw-0abc12345def67890", "State": "active" } ]
5. NATゲートウェイでプライベートサブネットのアウトバウンドを確保する
プライベートサブネット内のEC2がyumやapt updateでパッケージを取得するには、アウトバウンド(外向き)の通信経路が必要だ。NATゲートウェイはパブリックサブネット上に配置し、Elastic IP(静的グローバルIP)を割り当てる。# Elastic IPを取得する $ aws ec2 allocate-address --domain vpc # 実行結果 { "PublicIp": "54.249.xxx.xxx", "AllocationId": "eipalloc-0abc1234def567890", "Domain": "vpc" } # NATゲートウェイをパブリックサブネット上に作成する $ aws ec2 create-nat-gateway \ --subnet-id subnet-0pub1a234567890ab \ --allocation-id eipalloc-0abc1234def567890 \ --tag-specifications 'ResourceType=natgateway,Tags=[{Key=Name,Value=prod-nat}]' # 実行結果 { "NatGateway": { "NatGatewayId": "nat-0abc12345def67890a", "State": "pending", "SubnetId": "subnet-0pub1a234567890ab" } }
$ aws ec2 describe-nat-gateways \ --filter Name=nat-gateway-id,Values=nat-0abc12345def67890a \ --query 'NatGateways[0].State' # availableになったら次へ "available"
# プライベート用ルートテーブルを作成する $ aws ec2 create-route-table \ --vpc-id $VPC_ID \ --tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=prv-rtb}]' { "RouteTable": { "RouteTableId": "rtb-0prv1234567890def" } } # 0.0.0.0/0 をNATゲートウェイへ向けるルートを追加する $ aws ec2 create-route \ --route-table-id rtb-0prv1234567890def \ --destination-cidr-block 0.0.0.0/0 \ --nat-gateway-id nat-0abc12345def67890a { "Return": true } # プライベートサブネットにルートテーブルを関連付ける $ aws ec2 associate-route-table \ --route-table-id rtb-0prv1234567890def \ --subnet-id subnet-0prv1a234567890cd { "AssociationId": "rtbassoc-0def123456789abc0", "AssociationState": { "State": "associated" } }
マルチAZ設計パターン
本番環境では、1つのAZで障害が発生してもサービスが継続できるよう、最低2つのAZにサブネットを分散させる。マルチAZ冗長設計の詳細については、AWSマルチAZ冗長設計の実践ガイドも参照してほしい。CIDRの設計例(2AZ構成):
・pub-subnet-1a: 10.0.1.0/24(ap-northeast-1a)
・pub-subnet-1b: 10.0.2.0/24(ap-northeast-1b)
・prv-subnet-1a: 10.0.11.0/24(ap-northeast-1a)
・prv-subnet-1b: 10.0.12.0/24(ap-northeast-1b)
1b AZ用のサブネットを追加する:
$ aws ec2 create-subnet \ --vpc-id $VPC_ID \ --cidr-block 10.0.2.0/24 \ --availability-zone ap-northeast-1b \ --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=pub-subnet-1b}]' $ aws ec2 create-subnet \ --vpc-id $VPC_ID \ --cidr-block 10.0.12.0/24 \ --availability-zone ap-northeast-1b \ --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=prv-subnet-1b}]'
・コスト優先の場合: 1a AZにのみNATゲートウェイを置き、1b AZのプライベートサブネットも1a NATへルーティングする(1a AZ障害時にプライベートサブネットのアウトバウンドが止まるリスクあり)
・可用性優先の場合(推奨): 各AZにNATゲートウェイを1台ずつ設置し、AZ間の依存を排除する
可用性優先構成では、1b AZ用のNATゲートウェイとルートテーブルも同様の手順で作成し、「1b AZのプライベートサブネットは1b AZのNATへルーティングする」というAZ対称な構成にする。AZをまたいだ非対称なルーティングは、一方のAZで障害が起きたときに残ったAZのリソースまで影響を受ける直接の原因になる。
ALBを2つのパブリックサブネットに展開するには、subnetを両方指定する:
$ aws elbv2 create-load-balancer \ --name prod-alb \ --subnets subnet-0pub1a234567890ab subnet-0pub1b234567890ef \ --security-groups sg-0xxxxxxxxxxxxxxxx \ --type application
セキュリティグループとNetwork ACLとの役割分担
サブネット設計が整ったら、セキュリティグループ(SG)とNetwork ACL(NACL)を組み合わせて2層防御を構築する。それぞれの役割を整理しておく。| 機能 | セキュリティグループ | Network ACL |
|---|---|---|
| 適用単位 | ENI(インスタンス単位) | サブネット単位 |
| ステートフル | はい(戻りトラフィック自動許可) | いいえ(インバウンド・アウトバウンド個別設定が必要) |
| デフォルト動作 | 全拒否(許可ルールのみ記述) | 全許可(deny/allowどちらも記述可能) |
| 主な用途 | EC2・RDS個別のポート制御 | サブネット全体への粗いフィルタリング |
・セキュリティグループでリソース個別の細かいアクセス制御を行う(例: WebサーバーSGからDBサーバーSGへの3306のみ許可)
・Network ACLでサブネット単位の粗いフィルタリングを行う(例: プライベートサブネットへの特定ポートレンジをブロック)
NACLはステートレスのため、アウトバウンドを許可してもインバウンドの戻りトラフィック(エフェメラルポート: 1024~65535)も別途許可が必要になる点に注意が必要だ。シンプルな構成では「NACLはデフォルト全許可のままSGで制御する」方針が多い。NACLは「特定のCIDRブロックをサブネット全体でブロックしたい」ケースで追加的に使う。
よくある設計ミスとトラブルシュート
「プライベートサブネットのEC2がインターネットに出られない」
原因の切り分けは以下の順で確認する:1. NATゲートウェイのStateを確認する
$ aws ec2 describe-nat-gateways \ --filter Name=nat-gateway-id,Values=nat-0abc12345def67890a \ --query 'NatGateways[0].State' "available"
2. ルートテーブルの内容を確認する
$ aws ec2 describe-route-tables \ --filters Name=association.subnet-id,Values=subnet-0prv1a234567890cd \ --query 'RouteTables[0].Routes' # 0.0.0.0/0 のルートが存在し、NatGatewayId が正しいかを確認する
$ aws ec2 describe-nat-gateways \ --filter Name=nat-gateway-id,Values=nat-0abc12345def67890a \ --query 'NatGateways[0].SubnetId' "subnet-0pub1a234567890ab" # パブリックサブネットのIDが返ればOK
「EC2にパブリックIPが自動割り当てされない」
サブネットの「パブリックIPの自動割り当て」設定が無効になっている場合:$ aws ec2 modify-subnet-attribute \ --subnet-id subnet-0pub1a234567890ab \ --map-public-ip-on-launch # 成功時は何も出力されない
「RDSがインターネットに露出していないか確認したい」
RDSのサブネットグループが「パブリックサブネット」に紐づいていないかを確認する:# RDSインスタンスのサブネットグループ名を確認する $ aws rds describe-db-instances \ --query 'DBInstances[*].[DBInstanceIdentifier,DBSubnetGroup.DBSubnetGroupName,PubliclyAccessible]' # 実行結果例(PubliclyAccessible が false でも、サブネット配置を必ず確認する) [ ["prod-db", "prod-db-subnet-group", false] ] # サブネットグループのサブネットIDを取得する $ aws rds describe-db-subnet-groups \ --db-subnet-group-name prod-db-subnet-group \ --query 'DBSubnetGroups[0].Subnets[*].SubnetIdentifier' # 返ってきたサブネットIDがプライベートサブネットであることを確認する $ aws ec2 describe-route-tables \ --filters Name=association.subnet-id,Values=subnet-0prv1a234567890cd \ --query 'RouteTables[0].Routes[?DestinationCidrBlock==`0.0.0.0/0`].NatGatewayId' # nat-xxx が返ればNAT経由(プライベート)。igw-xxx が返ればパブリックサブネットに配置されている
PubliclyAccessible: false に設定されていても、パブリックサブネットに配置されているとセキュリティ設計の意図と実態がズレる。RDSは必ずプライベートサブネット上のサブネットグループに配置し、ルートテーブルでNATゲートウェイへのルートを確認することがセキュリティ監査の基本だ。まとめ
| やりたいこと | コマンド |
|---|---|
| VPCを作成する | aws ec2 create-vpc --cidr-block 10.0.0.0/16 |
| DNSホスト名解決を有効にする | aws ec2 modify-vpc-attribute --vpc-id vpc-xxx --enable-dns-hostnames '{"Value":true}' |
| サブネットを作成する | aws ec2 create-subnet --vpc-id vpc-xxx --cidr-block 10.0.1.0/24 --availability-zone ap-northeast-1a |
| IGWを作成してVPCにアタッチする | aws ec2 attach-internet-gateway --internet-gateway-id igw-xxx --vpc-id vpc-xxx |
| ルートテーブルにIGWルートを追加する | aws ec2 create-route --route-table-id rtb-xxx --destination-cidr-block 0.0.0.0/0 --gateway-id igw-xxx |
| ルートテーブルをサブネットに関連付ける | aws ec2 associate-route-table --route-table-id rtb-xxx --subnet-id subnet-xxx |
| NATゲートウェイを作成する | aws ec2 create-nat-gateway --subnet-id subnet-xxx --allocation-id eipalloc-xxx |
| NATゲートウェイのStateを確認する | aws ec2 describe-nat-gateways --filter Name=nat-gateway-id,Values=nat-xxx --query 'NatGateways[0].State' |
| プライベートサブネットのルートにNATを設定する | aws ec2 create-route --route-table-id rtb-xxx --destination-cidr-block 0.0.0.0/0 --nat-gateway-id nat-xxx |
| パブリックIP自動割り当てを有効にする | aws ec2 modify-subnet-attribute --subnet-id subnet-xxx --map-public-ip-on-launch |
| サブネットのルートテーブルを確認する | aws ec2 describe-route-tables --filters Name=association.subnet-id,Values=subnet-xxx --query 'RouteTables[0].Routes' |
NATゲートウェイの配置サブネットを誤ることと、ルートテーブルのアソシエーションを忘れることが最初のつまずきポイントになりやすい。CLIで手を動かして確認することで、どのリソースがどこにあり何と繋がっているかが確実に把握できる。
マルチAZ構成では「1aのプライベートは1aのNATへ、1bのプライベートは1bのNATへ」というAZ対称なルーティングが可用性設計の基本だ。AZをまたいだ非対称なルーティングは、障害時に影響範囲が広がる直接の原因になる。
サブネット設計の土台が整ったら、次はセキュリティグループとNetwork ACLを組み合わせた2層防御や、VPCエンドポイントを使ったS3へのプライベートアクセス設計へと進んでほしい。
VPCサブネット設計を「実務の型」として身につけませんか?
サブネットの切り方やルートテーブルの設定は、調べれば分かります。でも「なぜこの構成にするのか」「本番環境でマルチAZをどう設計するか」を自分の言葉で説明できますか?
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、『Linuxサーバー構築入門マニュアル(図解60P)』を完全無料でプレゼントしています。
「独学の時間がもったいない」「プロから直接、現場の技術を最短で学びたい」という本気の方には、2日で実務レベルのスキルが身につく【初心者向けハンズオンセミナー】も開催しています。
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます