🐙

EC2を用いたWebページ公開からSSL設定までの基本手順

2024/12/18に公開

AWSでシンプルなWEBアプリケーション(Flask)をEC2上で公開し、Route53で独自ドメインを紐づけ、最後にCertbotを用いてSSL化する一連の手順をまとめます。

基本手順と最低限の解説を行い、用語の解説や各ツールの選択根拠などを簡潔に触れています。

はじめに

  • 記事のゴール
    • EC2上でFlaskアプリを公開し、Route53で独自ドメインを紐づけ、HTTPS化まで行う一連の手順を紹介する
  • 前提知識
    • 基本的なAWSアカウント作成、AWS管理コンソール操作、SSH接続ができる程度の知識があること
  • 注意点(コストや方針など)
    • EC2インスタンスやRoute53利用、Elastic IPの固定割り当てなどで課金が発生する
    • 本記事はAmazon Linux 2を前提とする
    • Flaskは5000番ポートを解放し、その後、Nginx経由で80および443へ振り替える
    • example.comは独自ドメインに読み換える
    • 203.0.113.10は割り当てられたElastic IP(パブリックIPアドレス)に読み換える


構成図のイメージ

AWSマネジメントコンソールによる設定

VPC(仮想プライベートクラウド)内にパブリックサブネットを作成し、EC2インスタンスを配置する最小構成のネットワークを構築します。

VPC関連の用語解説
  • リージョン
    • AWSが提供する地理的なエリア
    • 各リージョンは独立しており、リージョン間のリソース共有には制限がある
    • リージョンの中に複数のアベイラビリティゾーン(AZ)が存在する
  • アベイラビリティゾーン
    • 各リージョン内に複数存在する物理的なデータセンター群
    • 高可用性のために複数のAZを利用して冗長構成を作成可能
    • 各AZは、同じVPC内でリソースを共有できるが、独立した環境を持つ
  • VPC(仮想プライベートクラウド)
    • ユーザーが定義した仮想ネットワークで、AWSリソースの配置とネットワーク設定が可能
    • VPCの中にパブリックサブネットとプライベートサブネットを作成できる
  • パブリックサブネット
    • インターネットと接続可能なサブネット
    • インターネットゲートウェイを介して外部アクセスができる
    • パブリックサブネットは、VPC内で設定され、EC2インスタンスなどのリソースを配置できる
  • プライベートサブネット
    • インターネットから隔離されたサブネット
    • 内部のみでのアクセスやNATゲートウェイを通じた制限付き外部通信が可能
    • プライベートサブネットもVPC内で作成され、外部から直接アクセスされないリソースを配置できる
  • ルートテーブル
    • サブネット内の通信経路を決定する設定
    • どのIPアドレスがどのゲートウェイを経由するかなどのルーティングを定義
    • ルートテーブルはVPCおよび各サブネットに関連付けられ、ネットワークトラフィックのルーティングを管理
  • NATゲートウェイ
    • プライベートサブネットから外部ネットワークに接続する際に使用されるゲートウェイ
    • 外部からの直接アクセスは不可
    • NATゲートウェイは、プライベートサブネット内のリソースが外部と通信するために利用され、VPC内で設定される
  • インターネットゲートウェイ
    • VPC内のリソースをインターネットと接続するためのゲートウェイ
    • パブリックサブネットに設定してインターネット通信を可能にする
    • インターネットゲートウェイはVPCに関連付けられ、パブリックサブネット内のリソースが外部と通信する際に使用される
  • セキュリティグループ
    • インスタンスレベルでのファイアウォール
    • 許可されたIPアドレスやポートに対するアクセス制御が可能
    • セキュリティグループはEC2インスタンスやその他のAWSリソースに適用され、VPC内でのトラフィックを制御
  • ネットワークACL
    • サブネットレベルでのファイアウォール
    • サブネット全体に対する許可・拒否ルールを設定し、セキュリティ強化が可能
    • ネットワークACLは各サブネットに関連付けられ、サブネット全体のトラフィックを制御

VPCおよび関連機能の作成

FlaskでWebページを公開するための必要最低限の構成とします。VPCに関する主な設定値は以下の通りです。

  • 作成するリソース
    • VPCなど
  • IPv4 CIDRブロック(IPv6 CIFRブロックはなし)
    • 10.0.0.0/16(任意)
  • アベイラビリティゾーン(AZ)の数:1
  • パブリックサブネット
    • 10.0.1.0/24(任意)
  • プライベートサブネットは不要
  • NATゲートウェイは不要
  • DNSオプション
    • DNSホスト名を有効化(AWSが提供するドメインの割り当て)
    • DNS解決を有効化(AWSが提供する内部のDNSサーバによる名前解決が可能になる)

今回の構成では、プライベートサブネット、NATゲートウェイの作成は不要です。
インターネットゲートウェイ(IGW)は上記を作成した段階で自動に作成されています。

サブネットの種類

パブリックサブネットとプライベートサブネットの違いは『インターネットへの接続が許可されているかどうか』です。

パブリックサブネットのルートテーブルにはインターネットゲートウェイ (IGW)へのアクセスを設定する一方、プライベートサブネットのルートテーブルにはインターネットゲートウェイ (IGW)へのアクセスの設定はなく、必要に応じてNATゲートウェイへのルートのみを設定します。

ルートテーブルの種類
  • メインルートテーブル
    • サブネットがデフォルトで自動的に関連付けられるルートテーブル
    • VPC作成時に自動的に1つのメインルートテーブルが作成される
    • メインルートテーブルのままでは削除できない
  • カスタムルートテーブル
    • 特定のサブネットに明示的に関連付けるルートテーブル
    • カスタムルートテーブルはVPC内で1つメインルートテーブルに設定できる
      • 設定することで元のメインルートテーブルはカスタムルートテーブルになる

EC2インスタンスの作成とElastic IP割り当て

パブリックサブネットに1台のEC2インスタンス(Linuxサーバ)を構築します。
セキュリティグループのインバウンドルールを設定し、Elactic IPを関連付けます。

  • Amazonマシンイメージ(AMI)
    • Amazon Linux 2
  • インスタンスタイプ
    • t2.micro(無料枠)
  • キーペア
    • 任意のファイル名で作成
  • セキュリティグループを作成(インターネットからのHTTP/HTTPSトラフィックを許可)
    • SSH(22)、HTTP(80)、 HTTPS(443)、 Flask(5000)、 ICMP(ping)許可
  • Elastic IPを割り当てて固定パブリックIPを確保
インスタンスタイプの種類

AWSではEC2インスタンスタイプによって料金が変わります。t2.microは無料枠対象の場合があり、低負荷のテスト用途に最適です。一方、t2.mediumやt3.smallなどにすればCPU・メモリが増えますが、その分コストも上がります。構築後は必ず使わないリソースを削除しましょう。

インスタンスタイプ 用途 特徴
t4g.micro 汎用 ARM、低コスト
t3.micro 汎用 バースト性能
m6g.large 汎用 Graviton2、軽量
m5.large 汎用 CPU・メモリバランス
c6g.large コンピューティング Graviton2、計算向け
c5.large コンピューティング 高CPU、科学計算
r6g.large メモリ最適化 Graviton2、分析向け
r5.large メモリ最適化 高メモリ、DB処理

今回はテスト環境であるため、SSH、HTTP、HTTPS、Flask用の5000番、ICMP等を全ての接続もとから許可していますが、本番環境では適切なアクセス制限が必要です。
※後工程でFlask用のデフォルトポート(5000)は不要となるため削除します。

EC2へのSSH接続と初期設定

EC2での操作はVSCode(Visual Studio Code)からSSH接続により実施します。

VSCodeによるEC2へのSSH接続

VSCodeを起動し、左ペインの拡張機能よりRemote-SSHをインストールし、以下のコマンドを実行します。

VSCodeよりSSHによる接続を実施
# SSHキーのパーミッション変更
$ chmod 400 ~/Downloads/MyAppKEY.pem
# SSHキーを適切な場所に移動
$ mv ~/Downloads/MyAppKEY.pem ~/.ssh/
# EC2インスタンスへの接続
$ ssh -i ~/.ssh/MyAppKEY.pem ec2-user@203.0.113.10
SSHトンネリング補足

インターネット経由で直接アクセスできない場合、VPNやSSHトンネリングを利用することも可能です。
SSHトンネルでローカルのポートをEC2のリモートポートに転送すれば、内部限定のサービスにローカルからアクセスできます。

ssh -i MyAppKEY.pem -L 8080:127.0.0.1:5000 ec2-user@203.0.113.10

このコマンドでローカルPCの8080番ポートにアクセスすると、リモートEC2の5000番ポートアプリケーションに接続できます。

クライアント側のSSH関連ファイル
  • ~/.ssh/config
    • 接続先情報・設定を簡略化するための設定ファイル
    • 接続先ごとの情報を記述し、sshコマンド実行時に省略入力や自動補完を可能にする
  • ~/.ssh/known_hosts
    • 接続先サーバの正当性を確認するためのセキュリティ上の記録を記録する
    • 初回接続時にサーバの公開鍵情報を記録し、2回目以降は照合により「なりすまし」を防ぐ
    • サーバ側の公開鍵情報の不一致を検知すると警告を表示し、安全でない可能性を指摘する

EC2ログイン後の初期設定(共通設定)

EC2に接続するとPythonやFlaskをインストールをする前に、以下の準備作業を進めます。

EC2の初期設定
# SELinuxの状態(有効?無効?どのモードか?)を確認する(デフォルトで無効)
$ sestatus
# SELinuxを一時的にpermissive(実質的に無効に近い状態)へ変更 ※再起動により戻る
$ sudo setenforce 0
# SELinuxを恒久的に無効化する ※再起動により反映
$ sudo sed -i 's/^SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config

# firewalldの状態確認(デフォルトで無効)
$ sudo systemctl status firewalld

# タイムゾーンの確認
$ timedatectl
# タイムゾーンを日本時間に変更
$ sudo timedatectl set-timezone Asia/Tokyo

# Chronyインストールおよび有効化(NTPによる時刻同期)
$ sudo yum install -y chrony
$ sudo systemctl enable chronyd
$ sudo systemctl start chronyd

SElinuxとfirewalldはAmazon Linux 2ではデフォルトで無効化されています。
本番環境ではEC2側で設定を有効化するかセキュリティグループによる接続元の制限を検討しましょう。

SSL証明書の更新などの際、時刻同期が重要になるためChronyにより設定をします。

sedコマンドの補足情報
  • テキスト設定ファイルの特定の文字列をスクリプト的に簡易編集できる。
  • sed -i 's/old/new/' file の形式でファイル中の文字列oldをnewに置換。

EC2へのパッケージのインストール

EC2でWebサーバを公開するために、主に以下のパッケージをインストールします。

ツール 役割・特徴
python3 現行バージョンのPython実行環境、Flaskアプリ実行に必要
pip3 Pythonパッケージ管理ツール、FlaskやGunicornをインストール
flask 軽量なPython製WEBフレームワーク、アプリ本体を作る
Gunicorn WSGIサーバー、Flaskを本番運用に適した形で起動可能
nginx 高性能Webサーバー・リバースプロキシとしてHTTP/HTTPS処理
epel-release EPELリポジトリを有効化、certbot等、一部ツールを入手可能
Certbot Let’s EncryptのSSL証明書取得・更新用ツール

PythonとFlaskのインストール

現在の最新版であるPython3環境ではパッケージ管理ツールとしてpip3を利用します。FlaskやGunicornをインストールするにはpip3が必要です。

Pythonのインストール
$ sudo yum update -y
# python3およびpip3がインストールされているか確認(もしなければインストール)
$ python3 --version
Python 3.7.16
$ pip3 --version
pip 20.2.2 from /usr/lib/python3.7/site-packages/pip (python 3.7)
$ sudo yum install -y python3 python3-pip
# pip3コマンドでFlaskをインストール
$ pip3 install flask
Flaskを実行する環境の違い

Flaskを実行する場合、以下のようなパターンがあります。

  • 一般ユーザでインストールする
  • rootユーザでインストールする
  • 仮想環境上(venvなど)にインストールする
  • コンテナ環境上(dockerなど)にインストールする

Flaskの起動とブラウザからの動作確認

Flaskで"Hello World from Flask!"を表示するだけのシンプルなプログラムを作成します。

Flaskの実行ファイルを作成する
$ mkdir flask_app
$ cd flask_app/
$ vi app.py
app.py
from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello():
    return "Hello World from Flask!"

if __name__ == '__main__':
    # 開発時はデバッグモードONでOK
    app.run(host='0.0.0.0', port=5000, debug=True)
viコマンドとnanoコマンド

viはUNIX系システムで伝統的に使われる強力なテキストエディタで、コマンドモードや編集モードの切り替えが必要で難易度がやや高めです。
一方でnanoは操作が直感的で、キーボードショートカットが画面下部に表示されるため初心者に優しい設計です。初めて触る場合はnanoがおすすめで、viは慣れてきたら試すとよいでしょう。

エディタ vi nano
保存 :wで保存、:wqで保存して終了 Ctrl+Oで保存
閉じる :qで終了(変更なし)、:q!で変更破棄して終了 Ctrl+Xで終了
基本操作 モード切替必須。iで挿入モード、Escでコマンドモードに戻る 画面下部にヒント表示。Ctrl+Kで行カット、Ctrl+Uでペースト

python3コマンドによりFlaskを実行し、ブラウザでhttp://203.0.113.10:5000にアクセスすることで、"Hello World from Flask!"が表示されることを確認します。

Flaskの実行ファイルを作成する
$ python3 app.py 
 * Serving Flask app 'app'
 * Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on all addresses (0.0.0.0)
 * Running on http://127.0.0.1:5000
 * Running on http://172.31.0.140:5000

動作確認が完了したらCtrl+Cにより実行を停止します。
この時点ではhttpsによる接続は失敗しますが、後の手順でSSLを有効化します。

WSGI(Gunicorn)のインストール

WSGI(Web Server Gateway Interface)は、PythonのWebアプリケーション(例えばFlaskやDjango)とWebサーバー間のやり取りを標準化する仕組みです。これにより、アプリケーションとサーバーを分離して開発・運用でき、同じWSGI対応サーバー上で様々なフレームワークのアプリを動かすことが可能になります。
今回はシンプルで分かりやすいGunicornをインストールします。

WSGIの特徴
項目 uWSGI Gunicorn
特徴 高機能・多機能 シンプル・軽量
設定難易度 設定オプション豊富でやや複雑 設定が比較的簡単
パフォーマンス 高いが設定次第で複雑 適度な性能を簡潔に実現
エコシステム 独自オプション多く、高度なチューニング可能 余計な機能が少なく、シンプルな運用に向く
Gunicornのインストール
$ pip3 install --user gunicorn

--userオプションを使わずsudo pip3 installなどでパッケージをインストールすると、システム全体にGunicornがインストールされ、システム標準のPython環境や他のパッケージとの互換性を損ねる可能性があります。

また、pipはroot権限でのインストールを非推奨としているため、以下のような警告が表示されます。

gunicornのインストール時の警告メッセージ
WARNING: Running pip install with root privileges is generally not a good idea.
Try `pip3 install --user` instead.

GunicornによるFlaskの起動

以下のコマンドによりGunicornを実行します。

Gunicornの実行
$ gunicorn --bind 0.0.0.0:5000 app:app
Gunicornコマンドのオプション
オプション 短縮形 意味/補足
--bind -b サーバーのバインドアドレス指定。例:-b 0.0.0.0:5000
--workers -w ワーカープロセス数指定。例:-w 3で3プロセス起動
--worker-class -k ワーカークラス指定(geventやeventletなど)。通常はsync
--timeout -t タイムアウト秒数指定。例:-t 30で30秒応答なしでワーカー再起動
--reload (短縮形なし) コード変更検知して自動リロード。本番環境では非推奨
--daemon (短縮形なし) デーモンモードでバックグラウンド実行

Gunicornが正常に起動し、ブラウザでhttp://203.0.113.10:5000にアクセスすることで、"Hello World from Flask!"が表示されることを確認します。

動作確認が完了したらCtrl+CまたはkillコマンドのTERMシグナルにより実行を停止します。

systemdによるGunicornのサービス化

systemdを用いてGunicornをサービス化することで、自動起動や再起動などが可能になり、起動順制御、ログ管理などの操作が標準的な手法で行えます。結果として、運用が容易になり、安定性や保守性が向上します。

systemdの特徴
  • systemdの特徴
    • Linux起動時に最初に起動するプロセス(PID 1)
    • ユニットファイルでサービス起動や依存関係の設定が可能
    • サービスを定義するにはユニットファイルを/etc/systemd/systemに配置する
    • ログやタイマーなど、サービス管理を一元化
  • Gunicornをsystemdで起動するメリット
    • 自動起動および自動再起動、他のサービスと連動した起動順序設定が可能となる
    • systemctlコマンドで一元管理できる
    • journalctlでログ管理が容易になる(start/stop/statusなど)
systemdユニットファイルを作成する
$ sudo vi /etc/systemd/system/gunicorn.service
/etc/systemd/system/gunicorn.service
[Unit]
Description=gunicorn daemon
After=network.target

[Service]
User=ec2-user
Group=ec2-user
WorkingDirectory=/home/ec2-user/flask_app
ExecStart=/home/ec2-user/.local/bin/gunicorn --bind 0.0.0.0:5000 app:app
Restart=always

[Install]
WantedBy=multi-user.target

ユニットファイルの主なセクションおよびディレクティブは以下の通りです。

セクション ディレクティブ 意味
[Unit] Description サービスの説明文。人が読める短い記述を設定
[Unit] After このユニットを起動する前に起動すべき他のユニットを指定(ここではnetwork.target)
[Service] User サービスを実行するユーザを指定
[Service] Group サービスを実行するグループを指定
[Service] WorkingDirectory サービス起動時の作業ディレクトリを指定
[Service] ExecStart サービス起動時に実行するコマンドを指定
[Service] Restart サービス異常終了時などの再起動方針を指定(alwaysで常に再起動)
[Install] WantedBy 有効化時にどのターゲットに関連付けるか指定(multi-user.targetでマルチユーザモード)
ユニットファイル(gunicorn.service)を読み込みサービスを有効化する
sudo systemctl daemon-reload
sudo systemctl start gunicorn
sudo systemctl enable gunicorn

サービスを起動し、ブラウザでhttp://203.0.113.10:5000にアクセスすることで、"Hello World from Flask!"が表示されることを確認します。

NginxのインストールおよびGunicornとの連携(80番ポートの公開)

NginxはFlaskアプリケーションを直接外部公開する場合に比べ、セキュリティやリクエストの効率化が向上するため推奨されます。また、SSL対応などが可能になり、運用面でもメリットがあります。

nginxのインストールと起動および有効化
$ sudo amazon-linux-extras install nginx1 -y
$ sudo systemctl start nginx
$ sudo systemctl enable nginx
nginxとnginx1の違い

nginxのインストール方法には主に以下のようなパターンがあります。

nginxのインストールと起動および有効化
$ sudo amazon-linux-extras install nginx1 -y
$ sudo yum install nginx

amazon-linux-extrasはAmazon Linux向けの公式パッケージ集で、サポートや安定性などが考慮されています。
yum単体でもnginxパッケージをインストールできますが、リポジトリによってはバージョンが古く非推奨な場合もあります。
amazon-linux-extrasを使うと、Amazon公式サポートでテスト済みのパッケージを入手できるため、
基本的にはamazon-linux-extras経由でのインストールが推奨されます。

Nginxの設定ファイルを作成

Nginxの設定ファイルを以下のように修正(作成)することで、Nginxが外部からのアクセスを標準的な80番ポートで受け取り、内部でバックエンドとして稼働するFlask(5000番ポート)へのトラフィックを中継します。(リバースプロキシ機能)
この方法を用いることで、アプリケーション自体を直接外部からアクセス可能な状態にせず、nginx経由で保護された環境下に置けます。
その結果、外部に余計なポートを公開することなく、アクセス制御やTLS終端などの運用管理が容易になります。

Nginxの設定ファイルを新規作成
$ sudo vi /etc/nginx/conf.d/my_flask_app.conf
/etc/nginx/conf.d/flask.conf (新規作成)
server {
    listen       80;
    server_name  203.0.113.10; # 後で独自ドメインに変更

    location / {
        proxy_pass http://127.0.0.1:5000;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $remote_addr;
    }
}
nginxの再起動(設定内容の反映)
sudo systemctl restart nginx

これでブラウザからhttp://203.0.113.10へアクセスするとFlaskアプリが表示される。

以降、5000番ポートへのアクセスは不要となるため、AWSマネジメントコンソールよりセキュリティグループから5000番ポートを閉じます。

独自ドメインとHTTPSによる接続(名前解決とSSL証明書の設定)

ここからはより実際のWebサーバに近づけるため、独自ドメインの設定とSSL化を実施します。

Route53での名前解決(AWSコンソールからの作業)

Route53でホストゾーンを作成し、NSレコードをドメイン登録業者側に設定します。
AレコードをEC2のElastic IPに紐づけると、http://example.comでアクセスできます。

Route53(名前解決)によりパブリックIPを使用せずにドメイン名で接続可能にします。

  • Route53から新しいホストゾーンを作成する
  • ホストゾーンの該当のドメイン情報からNSレコードを確認する
    • 4つ程のNSレコードが設定されています
  • ドメインの取得もとにnsサーバを指定します。
    • DNSの伝播を待つ(最大48時間ほど掛かる場合があります)
  • ホストゾーンの街頭のドメイン情報からシンプルルーティング
    • レコード名
      • 取得した独自ドメインを設定
    • レコードタイプ
      • A-IPv4アドレスと一部のAWSリソースにトラフィックをルーティングします。
    • 値/トラフィックのルーティング先
      • EC2のパブリックIPアドレス

独自ドメインにアクセスすることでRoute53による名前解決が行われ、Flask(Hello World from Flask!)へアクセスできるようになります。

レコードタイプ 用途 切り替え時間(例) 補足
Aレコード IPアドレスへの直接紐づけ TTL(例:300秒)後に反映 基本的なホスト名→IP
CNAMEレコード 別のドメイン名へのエイリアス TTL(例:300秒)後に反映 Aレコードを指す場合より若干遅延有り
AWS独自レコード(Alias) AWSサービス(ELB、CloudFront)へのエイリアス 通常即時性が高いが、max TTLと同程度 Route53内で最適化、課金は通常レコードと同程度

Route53のTTL(Time To Live)やドメインの切り替え浸透時間については以下のポイントを押さえましょう。

  • DNSのレコード(例:Aレコード)のTTLは、DNSキャッシュが生存する秒数。
  • TTLが300秒(5分)であれば、新しいDNS情報が反映されてから最大5分程度で大半のユーザに変更が行き渡る。
  • キャッシュを保持するDNSリゾルバによっては、既存のTTL分待たなければ最新情報にならない。そのため、切り替えには「現在設定中のTTL」程度の時間が目安となる。

なお、メールサーバなどでは逆引きレコード(PTR)の必要性が高いですが、Webサーバに関しては通常不要であることが一般的です。

Route53とbindの違い

Route53はAWSが提供する高可用性DNSサービスです。
bindと比べ、運用コスト、冗長性、安定性などさまざまな点で優れており、シンプルなUIによる容易な管理が可能です。

  • Route53
    • AWSのフルマネージドDNS、冗長・スケーラブルでWeb UI管理可能、料金は利用量に応じた課金
  • bind
    • 自分でDNSサーバを構築・管理、冗長化やセキュリティ確保は利用者責任、運用コストが高い

Nginxの設定ファイルを変更

/etc/nginx/conf.d/my_flask_app.confを修正し、server_nameを独自ドメインに変更してnginxを再起動します。

/etc/nginx/conf.d/my_flask_app.confの変更
server {
    listen       80;
    server_name  example.com; # 独自ドメインに変更
nginxの再起動により設定を反映
sudo systemctl restart nginx

名前解決が正常に実施されると、ブラウザからhttp://example.comと入力することで独自ドメインによるアクセスが可能となります。

SSL証明書によるHTTPS化(Certbot+Let’s Encrypt)

多くのWebサービスがHTTPSを前提としており、SSL化(HTTPS対応)は業界標準となっています。
WebサービスのSSL化には以下のようなメリットがあります。

  • セキュリティ
    • 通信内容が暗号化され、パスワードや個人情報が盗まれにくくなる
  • 信頼性
    • ブラウザで保護された接続と表示され、利用者に対して安心感を与える
  • SEO効果
    • 検索エンジンで、HTTPS対応サイトが優先的に評価されやすくなる

Amazon Linux 2では、python3-certbot-nginxパッケージが標準リポジトリに含まれず、certbotやpython3-certbot-nginxをそのままyum installできない場合が多いです。そのため、epel-releaseを導入したうえで、python2-certbot-nginxをインストールする方法が一般的です。

certbotのインストール
# EPELリポジトリを有効化
$ sudo amazon-linux-extras install epel -y
# Certbotとnginxプラグインをインストール
$ sudo yum install -y certbot python2-certbot-nginx
証明書の取得
$ sudo certbot --nginx -d example.com

certbotコマンドを実行すると、メールアドレスの入力と利用規約への同意が求められます。
入力が完了すると、Let’s Encrypt証明書を取得し、nginxの設定ファイルが自動的にHTTPS対応へ書き換えられます。

ファイル名 意味
cert1.pem サーバ証明書(サイト固有の証明書)
chain1.pem 中間証明書のみを含むチェーン
fullchain1.pem サーバ証明書と中間証明書が結合された完全なチェーン
privkey1.pem 秘密鍵(証明書と対応するプライベートキー)
/etc/nginx/conf.d/my_flask_app.conf(自動更新)
server {
    server_name  example.com; # 後で独自ドメインに変更

    location / {
        proxy_pass http://127.0.0.1:5000;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $remote_addr;
    }

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}
server {
    if ($host = example.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot

    listen       80;
    server_name  example.com;
    return 404; # managed by Certbot
SSL化を実装する機能の違い

WebサーバをSSL化する場合、主に以下のような選択肢があります。

  • certbot
    • Let’s Encryptから無料の実用的な証明書を自動取得・更新できるツール
    • EC2のNginxに直接SSL設定可能
  • openssl
    • 手動で自己署名証明書を発行可能。
    • ブラウザは自己署名(オレオレ証明書)を信頼しないため警告が表示される
  • AWS Certificate Manager
    • AWSが提供する証明書管理サービス
    • ALBやCloudFrontなどAWSサービスで使用する際に適している

EC2上でNginxを直接SSL化したい場合、Let’s Encrypt + certbotが最も手軽で安価(無料)の方法です。

snapとyumの違い

Certbotは頻繁な更新や新機能追加があるため、ディストリビューション標準リポジトリが古い場合があります。snapを使えば、Certbot開発元が直接最新パッケージを提供でき、常に新しいバージョンを取得しやすくなります。そのため、Certbot公式ドキュメントではsnap経由のインストールがよく薦められています。

  • snap
    • Canonicalが提供するパッケージシステム
    • バージョン管理が容易で依存関係も隔離しやすい
    • Amazon Linux 2ではデフォルトでsnapが入っていないため、導入に追加手順が必要
  • yum
    • RedHat系ディストリビューションの標準パッケージ管理ツール
    • Amazon Linuxはyumが標準でサポートされている
    • EPELを有効化することでcertbotなどを簡易に導入可能

基本的にはシンプルなyum+EPELによる標準パッケージのインストールで問題ありませんが、インストールに失敗したり最新機能が必要な場合はsnap経由でのインストールも有効です。

Let’s Encrypt証明書の自動更新

認証局が発行するSSL証明書は有効期限が1年〜2年程が一般的ですが、Let’s Encryptはセキュリティ上の観点から短期証明書(90日)を採用しています。

更新忘れの心配や運用の手間を軽減するために自動更新の設定が有効です。

Let’s Encrypt証明書を自動更新するにはcronまたはsystemd timerの機能を利用します。
初心者にはcronが簡単でわかりやすくおすすめですが、すでにsystemdに慣れている場合や、ログやサービス管理をsystemdで統一したい場合はsystemd timerでの管理が一元的で便利です。

以下のコマンドで事前に更新手順が正しく動くかテストしてから本番運用するようにしましょう。

自動更新のテスト
certbot renew --quiet --dry-run

いずれの手順でも24時間周期で「有効期限が30日未満になっていないか?」をチェックし、有効期限が迫っている場合は証明書を更新する流れとなります。

cronでの自動更新

以下のコマンドでcronを編集し、毎日適当な時間にcertbot renewを実行する設定を追加します。

毎日午前3時に更新処理を試す方法
$ crontab -e
0 3 * * * /usr/bin/certbot renew --quiet  

systemd timerでの自動更新

/etc/systemd/system/の配下に以下のユニットファイルを用意します。

certbot-renew.service
[Unit]
Description=Certbot Renew

[Service]
Type=oneshot
ExecStart=/usr/bin/certbot renew --quiet
certbot-renew.timer
[Unit]
Description=Daily renewal of Let's Encrypt certificates

[Timer]
OnCalendar=daily
Persistent=true

[Install]
WantedBy=timers.target 
ユニットファイルの再読み込みと有効化
sudo systemctl daemon-reload
sudo systemctl enable certbot-renew.timer
sudo systemctl start certbot-renew.timer

OnCalendar=dailyの設定により、デフォルトでは一日一度、特定の時刻(通常は真夜中)に有効期限のチェックおよび(必要に応じて)証明書の更新が実行されます。

補足情報と注意点

ここまでの説明でタイトルにある『EC2を用いたWebページ公開からSSL設定までの基本手順』は完了です。

ここからは手順を実行するための補足情報や注意点を簡単にまとめます。

EC2のバックアップとリストア

バックアップは、システムやアプリケーションに変更を加える前の状態を安全に保存し、万が一のトラブル発生時に以前の正常な状態へ迅速に戻すための手段です。
これにより、不測の障害やデータ損失が起きたとしても、安定した運用を維持しやすくなります。
加えて、バックアップを効果的に活用するためには、一定のベストプラクティスに従うことが望まれます。
たとえば、週単位や月単位といった定期的なバックアップ取得、OSアップデートやパッケージ追加・削除など重要な変更を行う前の事前取得、導入するバックアップ手段やその運用手順の明確化、セキュリティやアクセス制御を考慮したバックアップデータの安全な保管などが挙げられます。
こうした取り組みにより、万が一の事態にも素早く対応できる環境が整います。

主なバックアップ手法と特徴

  • AMI(Amazon Machine Image)
    • OS、アプリケーション、設定を丸ごと保存
    • 同一構成のEC2を容易に再起動可能
    • 環境全体を簡便に復元できるため、初めてのバックアップに最適
  • EBSスナップショット
    • EBSボリューム単位でバックアップ
    • AMI作成時にも内部的に利用される
    • データディスクのみや部分的なバックアップに有用
  • AWS Backup
    • 複数のAWSリソース(EC2、RDS、EBSなど)をポリシーベースで自動バックアップ
    • 運用自動化やマネージドな管理が可能
  • Launch Template
    • インスタンス起動時のパラメータを保存
    • 状態(アプリやデータ)は含まないため、完全なバックアップには不向き

システム全体を丸ごと復元したい場合はAMIがシンプルで分かりやすいです。
一方で、定期的な自動取得や複数リソース対応が必要ならAWS Backupを検討します。

バックアップとリストアの手順

AMIのバックアップおよびリストア手順は以下の通りです。

  • バックアップ手順
    • AWSマネジメントコンソールで対象EC2を選択
    • イメージの作成からAMIを作成
    • 必要に応じてスナップショットが内部的に生成され、AMIが完成
  • リストア(復旧)手順
    • 作成済みのAMIから「Launch Instance」を実行して新規EC2を起動
    • 復旧後、Route53のDNS設定やElastic IPの再関連付けなど、ネットワーク設定を調整
    • 証明書やアプリケーション設定もAMIから復元されるため、ほぼ同一環境で稼働開始可能
  • リストア後の必要な変更
    • DNS(Route53)やElastic IPの再割り当て
    • セキュリティグループやIAMロールなどの確認
    • 必要に応じてライセンスやキーなどの再設定

上記以外にも、TerraformやCloudFormationなどを利用することでインフラ構成をコード化(IaaC)し、バックアップおよび復旧時に構成の再現が可能です。

定期的なテスト復旧で、実際に問題なく元の環境に戻せることを確認すると、万が一の際も安心です。

セキュリティに関する考慮事項

本手順では、EC2インスタンス上でWebアプリケーションを公開するにあたり、外部からのアクセスを想定しています。これに伴い、以下の点を中心にセキュリティ対策を行うことが望まれます。

セキュリティグループによるアクセス制御

本手順では、EC2インスタンスを外部からアクセス可能な環境として構築するため、公開するポートは必要最低限に留めることが推奨されます。

たとえば、管理用のSSHアクセスは22番ポート、一般的なWebアクセス用のHTTP/HTTPSは80番および443番ポートのみを許可すると良いでしょう。また、管理用のSSH接続は特定の管理者IPアドレスだけに限定することで、不特定多数からの攻撃リスクを減らすことができます。

さらに、構築初期にはFlaskアプリケーションを5000番ポートで開発用に公開することがありますが、nginxをリバースプロキシとして設定してからは、不要な5000番ポートを閉じて許可範囲をより狭めることが望まれます。

firewalldやOSレベルのファイアウォール設定

セキュリティグループによる制御に加え、OS内部でもfirewalldを用いてトラフィックを制限することで、二重の防御層を形成できます。

このような多層防御は、セキュリティグループ設定に万が一の不備があった場合でも被害を最小限に抑えるために有効です。

パッケージ・ソフトウェアの最新版維持

サーバーの安全性を高めるには、OSやアプリケーション、関連ライブラリを常に最新の状態に保ち、セキュリティパッチを確実に適用することが重要です。

yum updateやamazon-linux-extras、epel-releaseなどを活用し、脆弱性を抱えた古いバージョンを放置せず、定期的なメンテナンスを行うことで、より安全な環境を維持できます。

Flaskのデバッグモード無効化

Flaskはapp.run(debug=True)でデバッグモードを有効化できますが、これを本番運用下で使用すると詳細なエラーメッセージや内部情報が外部から参照可能となり、セキュリティ上のリスクを伴います。本番公開前にはdebug=Falseとし、WSGIサーバ(Gunicorn)およびnginx経由での公開に移行して、安全かつ適切な本番環境を整えることが求められます。

HTTPS対応と証明書管理

通信を暗号化することで盗聴や改ざんを防ぐため、Let's Encryptで取得した証明書を用いたHTTPS対応が有効です。

証明書は自動更新設定(certbotによるrenew)を適用して有効期限切れを防ぎ、常に安全な通信路を確保するようにします。

バックアップ・復旧時の安全性

AMIやスナップショットを用いたバックアップ・復旧の場面でも、セキュリティは軽視できません。復旧直後にはセキュリティグループやRoute53、Elastic IPなどの再設定を見直し、初期状態から不要なポートやデバッグモードが有効になっていないかを確認します。
こうした点検により、復旧後も引き続き安全な環境を維持できます。

その他の考慮事項

IAMロールによる権限管理や、~/.ssh/configとknown_hostsファイルの適切な扱いなど、インフラ全体を通じたセキュリティ強化にも目を向けることが大切です。また、journalctlやCloudWatch Logsなどを用いた定期的なログ監視を実施し、不正アクセスの兆候を早期に発見することで、潜在的な脅威に速やかに対応できます。

これらの取り組みを総合的に行うことで、本番環境でも信頼性の高いセキュリティを確保し、安心してWebアプリケーションを運用できます。

費用が発生するサービスおよび費用発生条件例

運用中のEC2やEIP、Route53、SSL証明書などは常時利用で料金がかかります。必要なサービス以外は早めに停止・削除し、定期的に費用を確認することで無駄なコストを避けられます。

今回の手順の中で費用が発生する主な項目は以下の通りです。

  • EC2インスタンスの稼働時間:
    • インスタンスタイプ(t2.microなど)の利用時間に応じた料金が発生。
    • インスタンスを「停止」していても一部のコストが続く
    • t2.microは無料枠対象であれば低額または無料(無料枠なしなら数ドル/月程度)。
  • EC2のEBSストレージ(SSD等)費用:
    • EC2インスタンスに紐づくルートボリュームや追加ボリュームは、インスタンス停止中でも容量に応じて料金が発生。
    • EBSボリュームは数GB程度なら月額数十~数百円程度。1日あたりにするとごくわずか。
  • Elastic IP (EIP):
    • インスタンスに紐づけたままRunning状態で使っていれば追加料金はかからないが、EIPを割り当てているのに関連インスタンスを停止・解放して未使用状態になった場合、EIP維持費用がかかる。
    • EIPは未使用時でも月1~数ドル程度。1日数円~数十円レベル。
  • Route53:
    • ドメイン登録費用(初回取得・更新時)
    • DNSホスティング費用(レコード管理のため)が月額やクエリ数に応じて微量の料金が発生。
      • 1ドメインあたり月額0.5ドル程度+クエリ超過分。1日でみれば数円程度。
    • ドメイン取得維持費用:
      • ドメイン取得費用は年間契約なので日割りすれば1日あたり数円程度。
  • Let’s Encrypt証明書
    • 無料。発行や更新で費用はかからない。

この他、S3やCloudWatchなどの利用があれば、ログ保存用にS3を使えばS3費用が、CloudWatchで詳細なメトリクスやログを保持すれば、その保存量に応じた料金が発生する。

まとめ

今回の手順のまとめです。

  • VPCとパブリックサブネットを用意し、EC2インスタンスを構築
  • セキュリティグループでSSHやHTTP/HTTPS、必要なポートを開放
  • Python3、Flask、GunicornをEC2上にインストールし、Flaskアプリを起動
  • Nginxを導入して80番ポートで外部公開し、Flaskへのリバースプロキシ設定
  • Route53で独自ドメインをEC2のIPに紐づけ、DNSを適用
  • EPELリポジトリ経由でcertbotとpython2-certbot-nginxをインストールし、Let's Encrypt証明書を取得してHTTPS対応
  • AMIやEBSスナップショット、AWS Backupでバックアップ・復旧手法を確保
  • コストやセキュリティにも配慮し、不要ポートは閉じ、Flaskデバッグモードは本番無効化

本記事では、AWS上での基本的なWebアプリ公開からSSL対応までの流れを示し、関連用語やツールの差異を簡潔に紹介しました。これらを活用することで、シンプルな構成でも独自ドメインとHTTPS対応のWebサイトを運用しやすくなります。

Discussion