概要
マインクラフト統合版をローコストでサーバー運用する。(2025/10/01現在)
以下の順で解説
- 背景
- 料金
- 参考記事
- 環境
- 環境選定
- 運用サーバインスタンスの作成
- サーバインスタンスにログイン
- IAMロールの作成
- インスタンス自動起動用関数URL作成
- マイクラのお試し起動
- 自動シャットダウン用クーロン作成
- Tips
- おまけ
背景
マインクラフト公式でもrealmsをつかってサーバを立てられるけど、~2人で3.99ドル/月、~10人で7.99ドルと結構高いので、サーバを作ることにした。
料金
結論から言うと、課金詳細は以下になった
| 項目 | 課金詳細 | 1ヶ月料金ドル(1週間で26時間遊んだ場合) |
|---|---|---|
| EC2 | $0.0304 per on demand instance hour | 3.2832 |
| EBS | $0.096 per GB-month | 0.288※ |
| IP | $0.005 per In-use | 0.56 |
| 合計 3.9696 |
※ 2週間くらい運用して容量3GB未満なので、一旦EBSは3GBとする
1日4時間弱までであれば、realmsの3.99ドル/月より安く済みそう
参考記事
- https://chatgpt.com/?model=auto
- https://qiita.com/ekko/items/851fb0cc9eddb9680071
- https://qiita.com/drummers-high/items/a403bf350faac393a9a6
- https://www.minecraft.net/ja-jp/download/server/bedrock
ソースコード
今回使用したソースコードは以下においてます
https://github.com/keiich/mincraft_operation
(2025/11/03現在 マインクラフトの自動アップデート機能も追加しました)
環境
| 項目 | 詳細 |
|---|---|
| クライアントPC | windows11 |
| クラウド環境 | AWS EC2 |
| マインクラフトサーバ | 統合版 (Bedrock Edition) |
| EC2インスタンスOS | Ubuntu |
環境選定
クラウドサーバ
ロリポップfor gamersや、シンクラウドなどいろいろあったが、以下の理由でec2を選んだ。
- realmsより安く済ます
- これが一番大きい理由
- 従量課金制
- 後述するが、ログインユーザが誰もいない場合は自動でサーバを落とすようにしてサーバ代を節約する
マインクラフトサーバ
java版で作っている記事が多かったので最初はjava版で構築しようとしていたが、
統合版だとPC以外でも遊べるので統合版にした。
EC2インスタンスOS
最初はよく使われているaws linux 2023を使おうとしたが、
統合版だとBedrock Editionのzipファイルを解凍して、実行時に以下のように、使っているLinux環境のライブラリのバージョンが、bedrock_serverが要求するものより古い
エラーが発生したため、ubuntuにした。
./bedrock_server: /lib64/libcurl.so.4: no version information available (required by ./bedrock_server)
./bedrock_server: /lib64/libm.so.6: version GLIBC_2.35' not found (required by ./bedrock_server)
運用サーバインスタンスの作成
まずはawsにログイン(「コンソールにサインイン」)。アカウントがなければ右上の「アカウントを作成」から。ここでクレカなどの登録が必要になる。
https://aws.amazon.com/jp/
サインインしたらヘッダーにて「アジアパシフィック(東京)」を指定 → 検索窓でec2と検索
でてきたダッシュボードから「インスタンスを起動」
※ここで「アジアパシフィック(東京)」を指定することを忘れない。ラムダ関数を制作するときにも同じ領域を選ぶことになるため。

設定項目
| 項目 | 詳細 | 備考 |
|---|---|---|
| 名前 | 適当 | |
| Amazon マシンイメージ (AMI) | Ubuntu Server 24.04 LTS (HVM), SSD Volume Type | |
| インスタンスタイプ | t2.small | 後ほどスケール変更可能(なはず) |
| キーペア名 | 新しいキーペアの作成を押下 | (後述) |
| ネットワーク設定 | (後述) |
それ以外はデフォルト
キーペア名
新しいキーペアの作成を押下したら、以下を選ぶ
キーペア名: 任意
キーペアのタイプ: RSA
プライベートキーファイル形式: .pem
キーペアを作成したらローカルPCにダウンロードされるので、ダウンロード先を把握しておく
ネットワーク設定
右の編集ボタンを押下
インバウンドセキュリティグループのルール
- ローカルPCからのアクセス
| 項目 | 値 | 備考 |
|---|---|---|
| タイプ | ssh | |
| ソースタイプ | 自分のIP | ローカルPCのIPアドレスが勝手に追加される |
- マインクタラフトサーバからのアクセス
「セキュリティグループを追加」を2回押下して以下2つの設定も追加
| 項目 | 値 | 備考 |
|---|---|---|
| タイプ | カスタムUDP | |
| ポート範囲 | 19132 | IPV4用 |
| ソースタイプ | 任意の場所 |
| 項目 | 値 | 備考 |
|---|---|---|
| タイプ | カスタムUDP | |
| ポート範囲 | 19133 | IPV6用 |
| ソースタイプ | 任意の場所 |
それ以外はデフォルトの設定。
最後に「インスタンスを起動」を押下すると、インスタンスが立ち上がる。
サーバインスタンスにログイン
linux系のコマンドが使えるため、自分はgit bashをインストールして作業した。
以下のリンクからdownloadできる。
https://gitforwindows.org/
pemファイルを用意
インスタンス作成時に作ったキーペア(pemファイル)を、
~/.ssh/[任意の名前].pem
に保存する。
ファイルの中身は以下のような秘密鍵形式。
-----BEGIN RSA PRIVATE KEY-----
文字列
-----END RSA PRIVATE KEY-----
保存手順は以下参考
# ダウンロード先から、.sshディレクトリの中へコピー
cp /c/Users/[ユーザ名]/Downloads/[ファイル名(my-key-pair)].pem ~/.ssh/
# pemファイルの権限を変更
chmod 400 ~/.ssh/[ファイル名].pem
以下コマンドでログイン
(例)
ssh -i ~/.ssh/[ファイル名].pem ubuntu@52.194.221.92
ここで、
| 1 | 2 |
|---|---|
| ubuntu | AMI 名(基本的にこの名前になる) |
| 52.194.221.92 | パブリック IPv4 アドレス(例) |
である。
それぞれ、EC2→リソースのインスタンス(実行中)→インスタンスの状態=running のフィルターを外す
で出てきたインスタンスIDを押下することで確認できる。
中に入ると、以下のようになる。
(例)
ubuntu@ip-52.194.221.92:~$
IAMロールの作成
これがないと、以下で作成する関数URLでEC2の操作ができない。
- awsの検索窓でiamと検索してでてきたiamセクションを押下
- 左の一覧の「アクセス管理」→「ロール」を押下
- 右上の「ロールを作成」を押下
- 「サービスまたはユースケース」でLambdaを選んで「次へ」
- 許可ポリシーに以下2つ選んで次へ
| 項目 | 説明 |
|---|---|
| AmazonAPIGatewayPushToCloudWatchLogs | lambdaの成功や失敗ログをcloudwatchのuiで確認できるようにする(いらないかも) |
| AWSQuickSetupStartStopInstancesExecutionPolicy | Lambda関数がEC2を起動・終了できる権限 |
- 「ロール名」に適当に名前を入れて、「ロールを作成」
インスタンス自動起動用関数URL作成
次にインスタンスを自動起動させるための関数URLを作成します。
- awsの検索窓で「lambda」と入力、候補で出てきた「lambda」を押下
- 右上の「関数を作成」を押下
- 以下項目を設定し、再度「関数を作成」を押下
| 項目 | 詳細 | 備考 |
|---|---|---|
| 関数名 | 任意の名前 | |
| ランタイム | Python 3.12 | 最新のバージョンでok |
| アーキテクチャ | デフォルト | x86_64 |
| 実行ロール | 先ほど作ったロールを選択 | 「デフォルトの実行ロールの変更」→「既存のロールを使用する」を押下 |
| 関数URLを有効化 | チェックする | 「その他の構成」を押下。認証タイプは「NONE」※1 |
※1 誰でもURLを押せるようになるので、そうしたくない場合は別途調べてください。自分は友達を信じて公開中。
インスタンスが出来上がったら、「コード」のコードソース画面で以下を書き込む
import boto3
import time
import requests
def lambda_handler(event, context):
path = event.get("rawPath", "")
if path.endswith("favicon.ico"):
return {
"statusCode": 204,
"body": "" # 何もしない
}
# EC2インスタンスID
instance_id = '[インスタンスID]'
# EC2インスタンスを起動するためのパラメータ
ec2 = boto3.client('ec2')
# まず現在の状態を確認
instance = ec2.describe_instances(InstanceIds=[instance_id])
state = instance['Reservations'][0]['Instances'][0]['State']['Name']
webhook_url = '[投稿したいdiscordチャンネルのwebhook_url]'
if state == 'running':
# すでに起動している場合
public_ip = instance['Reservations'][0]['Instances'][0].get('PublicIpAddress', '未割り当て')
data = {
'content': f'インスタンスはすでに起動中です。\rサーバーアドレス:{public_ip}'
}
requests.post(webhook_url, json=data)
return public_ip
else:
response = ec2.start_instances(InstanceIds=[instance_id])
# EC2インスタンスが起動するのを待つ
instance_running = False
while not instance_running:
instance = ec2.describe_instances(InstanceIds=[instance_id])
state = instance['Reservations'][0]['Instances'][0]['State']['Name']
if state == 'running':
instance_running = True
else:
time.sleep(5)
# EC2インスタンスのパブリックIPアドレスを取得する
instance = ec2.describe_instances(InstanceIds=[instance_id])
public_ip = instance['Reservations'][0]['Instances'][0]['PublicIpAddress']
#DiscordにWebhookを送信
data = {
'content': f'インスタンスは起動しました\rサーバーアドレス:{public_ip}'
}
requests.post(webhook_url, json=data)
return public_ip
ここで、インスタンスID、webhook urlはそれぞれ
| 項目 | 詳細 |
|---|---|
| インスタンスID | EC2→リソースのインスタンスを押下して出てきたID |
| webhook_url※ | こちらで取得:https://zenn.dev/discorders/articles/discord-webhook-guide |
である
※このurlを取得することで特定のdiscordチャンネルに通知できます。通知が不要な場合はDiscordにWebhookを送信しているコードをコメントアウトでOK
コード解説
Favorite Icon取得用パスの場合は処理をスキップする
関数URLが実行されると、ほとんどの端末で
- タブのアイコンを取得(favicon)
- 本実行
というように同じルートパスで2回に分けてリクエストが送られる。
よって、pathの最後がfavicon.icoで終わるリクエストは無視するようにする
現在のインスタンスの状態を確認
a. すでに起動している場合
- サーバーアドレスと、起動している旨をdiscordに送信、クリックしたリンク先でもサーバーアドレスを返す
b. 起動していない場合
- インスタンスが起動するまでsleep(5秒)をループさせて待つ。
- 起動したら、サーバーアドレスと起動している旨をdiscordに送信、クリックしたリンク先でもサーバーアドレスを返す
レイヤーの選択
上記のコードでimportしているrequestsモジュールを使えるようにするために、レイヤーを追加する。
- 一番下までスクロールして「レイヤーを追加」を押下
- ARNを指定をを選択
- 「arn:aws:lambda:ap-northeast-1:770693421928:layer:Klayers-p312-requests:17」をコピペで張り付け
- 追加を押下
※レイヤーはこちら から参照する
リンクを押下したら、「ap-northeast-1 Asia Pacific (Tokyo)」のhtmlのリンクを押下、すると「arn:aws:lambda:ap-northeast-1:770693421928:layer:Klayers-p312-requests:17」がでてくる。
タイムアウトの設定
「設定」タブ→一般設定→編集から、
タイムアウトを3分に設定して、「保存」
サーバー起動
設定タブ→関数URLから「https://********.lambda-url.ap-northeast-1.on.aws/」を押下
数秒してサーバーアドレスが表示されればOK

discordへの投稿設定をしていれば以下投稿される

サーバを起動させるたびにパブリックIPが変化する。IPを固定させることもできるが、
保持するのに費用がかかる可能性があるため、サーバ停止するたびにIPを破棄する方針をとる
マイクラ お試し起動
以下コードと説明
mkdir mincraft;cd mincraft
# https://www.minecraft.net/ja-jp/download/server/bedrockから
# Ubuntuのダウンロードボタンを右クリック、リンクのコピーで取得
wget https://www.minecraft.net/bedrockdedicatedserver/bin-linux/bedrock-server-1.21.102.1.zip
sudo apt install unzip
unzip bedrock-server-1.21.102.1.zip
chmod +x bedrock_server
## 一番下に****を入力
vim server.properties
## お試し起動
LD_LIBRARY_PATH=. ./bedrock_server
# 「stop」でマイクラサーバを止める
マイクラサーバの細かな設定は、
minecraft/server.propertiesファイルを直接いじることで設定できる。設定後はマイクラサーバのリロードで反映
以下参照
https://hikicomoron.net/minecraft_server_properties_be
(難易度はデフォルトでeasyになっている)
マイクラ デーモン起動
EC2サーバが立ち上がったらマイクラサーバが勝手に立ち上がるようにする
/etc/systemd/system/minecraft.service ファイルをvimで編集
[Unit]
Description=Minecraft Bedrock Server
After=network-online.target
[Service]
WorkingDirectory=/home/ubuntu/minecraft
User=ubuntu
# ExecStartPre/ExecStartはサーバ起動時のコマンド
# キャッシュが残っていると、「スキンが壊れています」と表示され
# サーバーに入れない可能性があるため、
# 起動前に premium_cache をリネームしてバックアップ
ExecStartPre=/bin/bash -c 'if [ -d premium_cache ]; then mv premium_cache premium_cache_backup_$(date +%%Y%%m%%d_%%H%%M%%S); fi'
# ログファイル名を現在時刻をサフィックスにし、ファイル名をcurrent_logfile.txtに保存。(後ほど使うため)
# screenコマンドでマイクラサーバをバックグラウンド実行。
# (できればtmuxのほうがいいらしいが、デフォルトで入っているscreenにした)
ExecStart=/usr/bin/screen -DmS minecraft bash -c 'logfile="bedrock_server_$(date +%%Y-%%m-%%d_%%H).log"; echo $logfile > /home/ubuntu/minecraft/current_logfile.txt; ./bedrock_server >> $logfile 2>&1'
# サーバリロード時のコマンド
ExecReload=/usr/bin/screen -p 0 -S minecraft -X eval 'stuff "reload"\\015'
# サーバ停止時のコマンド
## マインクラフトは基本的に3分おきに自動保存されているが、タイミングによってはサーバ停止時にデータが保存されない可能性もあり得るので、以下コマンドで安全に保存する
ExecStop=/usr/bin/screen -p 0 -S minecraft -X eval 'stuff "say Server Shutdown. Saving map..."\\015'
ExecStop=/usr/bin/screen -p 0 -S minecraft -X eval 'stuff "save hold"\\015'
ExecStop=/bin/sleep 5
ExecStop=/usr/bin/screen -p 0 -S minecraft -X eval 'stuff "save resume"\\015'
ExecStop=/bin/sleep 2
ExecStop=/usr/bin/screen -p 0 -S minecraft -X eval 'stuff "stop"\\015'
Restart=on-failure
RestartSec=60s
[Install]
WantedBy=network-online.target
※マイクラサーバ起動時に書き込まれるbedrock_server_$(date +%%Y-%%m-%%d_%%H).logには、ユーザのログイン、ログアウトタイミングが書き込まれる
ログファイルの中身は以下のような感じ
details
[2025-10-09 11:35:37:848 INFO] Starting Server
[2025-10-09 11:35:37:848 INFO] Version: 1.21.111.1
[2025-10-09 11:35:37:848 INFO] Session ID: cb6a695c-6ef1-4a13-9666-ab985ae5abaa
[2025-10-09 11:35:37:848 INFO] Build ID: 37061739
[2025-10-09 11:35:37:848 INFO] Branch: r/21_u11
[2025-10-09 11:35:37:848 INFO] Commit ID: 9b62a5d98ee63f3c28bbee06536d7acdf94f00e2
[2025-10-09 11:35:37:848 INFO] Configuration: Publish
[2025-10-09 11:35:37:856 INFO] Level Name: Bedrock level
[2025-10-09 11:35:37:901 INFO] No CDN config file found at: cdn_config.json for dedicated server
[2025-10-09 11:35:37:901 INFO] Game mode: 0 Survival
[2025-10-09 11:35:37:901 INFO] Difficulty: 2 NORMAL
[2025-10-09 11:35:37:923 INFO] Content logging to console is enabled.
[2025-10-09 11:35:41:452 INFO] Opening level 'worlds/Bedrock level/db'
[2025-10-09 11:35:41:701 INFO] [SERVER] Pack Stack - None
[2025-10-09 11:35:43:496 INFO] IPv4 supported, port: 19132: Used for gameplay and LAN discovery
[2025-10-09 11:35:43:496 INFO] IPv6 supported, port: 19133: Used for gameplay
[2025-10-09 11:35:43:543 INFO] Server started.
[2025-10-09 11:35:58:271 INFO] Player connected: userA, xuid: ******
[2025-10-09 11:36:04:453 INFO] Player Spawned: userA xuid: ******, pfid:
[2025-10-09 12:58:21:648 INFO] Player disconnected: userA, xuid: ******, pfid:
[2025-10-09 13:00:02:721 INFO] Saving...
[2025-10-09 13:00:07:432 INFO] Changes to the world are resumed.
デーモンを起動させる
## 新しく作成・編集した .service ファイルを systemd に再読み込みさせる
sudo systemctl daemon-reload
## サーバー起動時(OS起動時)に自動で起動するよう設定
sudo systemctl enable minecraft.service
## すぐにMinecraftサーバーを起動
sudo systemctl start minecraft.service
起動後の状態確認
systemctl status minecraft.service
running になっていればOK
以下コマンドで起動中のデーモンのログを確認できる(マイクラサーバのログではない)
sudo journalctl -u minecraft.service -f
自動シャットダウン用クーロン作成
以下のpythonファイルをcronに登録することで、一定時間おきに実行させ、
ログインユーザがいなかったらEC2サーバごとシャットダウンさせる。
#!/usr/bin/env python3
import datetime
import os
import requests
# current_logfile.txt から実際のログファイル名を読む
logfile_marker = "/home/ubuntu/minecraft/current_logfile.txt"
# 以前設定したdiscordのwebhook_url
webhook_url = '[webhook_url]'
try:
with open(logfile_marker, "r") as f:
file_name = f.read().strip()
except FileNotFoundError:
print("LOG FILE MARKER NOT FOUND!")
exit(1)
# ログファイルパス
file_path = f"/home/ubuntu/minecraft/{file_name}"
# カウンタ初期化
in_cnt = 0
out_cnt = 0
starting = True
server_start_seconds = None # サーバー起動時刻(秒)
try:
with open(file_path, 'r') as f:
lines = f.readlines()
except FileNotFoundError:
print("LOG FILE NOT FOUND!")
exit(1)
for line in lines:
line = line.strip()
if not line:
continue
if not line.startswith('['):
continue
parts = line[1:].split()
if len(parts) < 2:
continue
timestamp_str = parts[1] # 'hh:mm:ss:ms'
hms = timestamp_str.split(':')
if len(hms) < 3:
continue
hour, minute, second = int(hms[0]), int(hms[1]), int(hms[2])
log_seconds = hour * 3600 + minute * 60 + second
# サーバー起動時刻を Server started. の行から取得
if server_start_seconds is None and 'Server started.' in line:
server_start_seconds = log_seconds
continue
# プレイヤー接続・切断の判定
if 'Player connected:' in line:
in_cnt += 1
if 'Player disconnected:' in line:
out_cnt += 1
# 起動15分以内か判定
if server_start_seconds is not None:
now = datetime.datetime.now()
now_seconds = now.hour*3600 + now.minute*60 + now.second
diff = now_seconds - server_start_seconds
if diff < 0:
diff += 24*3600 # 日をまたいだ場合
starting = diff < 15*60
else:
# Server started. の行が見つからなかった場合
print("Server start time not found in log.")
starting = True # 安全のため起動中扱い
# 判定結果を出力
if starting:
print('Server is starting... (shutdown disabled for first 15 min)')
elif in_cnt > out_cnt:
print('Someone is logged in.')
else:
print('No players online. Shutting down...')
data = {
'content': f'ログインユーザがいなくなったため、インスタンスはシャットダウンしました。'
}
requests.post(webhook_url, json=data)
os.system('sudo shutdown -h now')
上記のファイルをクーロンに登録する。
以下は10分おきにログインユーザを確認する例
# 編集
ubuntu@ip-172-31-39-51:~/minecraft$ sudo crontab -e
# 確認
ubuntu@ip-172-31-39-51:~/minecraft$ sudo crontab -l
*/10 * * * * /usr/bin/python3 /home/ubuntu/ActiveMonitoring.py >> /home/ubuntu/cron_error.log 2>&1
以上
Tips
バージョンアップ
- サーバに置いているマインクラフトのバージョンとユーザが起動するマインクラフトのバージョンを合わせないと起動できない。
- バージョンアップはbedrockの最新のバージョンをwget
- minecraft/worldディレクトリさえあれば前のデータを移し替えらえる
座標表示の仕方
minecraft/server.propertiesを編集して以下trueにする
allow-cheats=true
マイクラ内設定変更の権限を与える
# screen起動中のプロセスに入る
screen -r minecraft
# 設定変更権限を与える
op [マイクラユーザID]
# ctrl+a,dで抜け出す
サーバリスタート
sudo systemctl restart minecraft
マイクラのゲーム内に入り、チャットボックスで座標表示コマンドを入力
/gamerule showCoordinates true
表示されたらallow-cheatをfalseに戻しておく
EC2サーバでのsudo実行においてパスワードを求められた時
sudo passwd ubuntu
でパスワード設定しておく
おまけ
AWS登録するだけでクレジットが140ドルくらいもらえてた(1年の期限付き 20251001現在 )
ので絶対realmsより安い!!!!!!




