§まえがき

Ubuntu 26.04 LTS をインストールしてWebサーバーを構築します。

2016年頃から Ubuntu LTS を用いてVPSでWebサーバーを構築しています。2022年(Ubuntu 22.04 LTS)まではメモリ1GのVPSを用いていましたが、メモリ不足でOSやミドルウェアが動かせなくなってきたので2024年(Ubuntu 24.04 LTS)はメモリ4GのVPSを用いました。

§サーバーを用意する

2022年までは「さくらVPS」のメモリ1Gを使っていましたがメモリ4G以上となると料金も高くなるため、2024年以降は「XServer VPS」を用いています。

メモリは4Gあれば小規模な用途には十分ですが、メモリおよびストレージ容量に余裕を持たせたいため、2026年(Ubuntu 26.04 LTS)はメモリ12GのVPSを用いることにします。

用意したサーバーのOS初期ユーザー名とIPアドレスを確認します。

§秘密鍵と公開鍵の作成

ssh-keygen -t ed25519 -C 'yourmail@example.jp'

§DNS RRs の設定

host type value
example.org A 203.0.113.123
*.example.org A 203.0.113.123
example.org MX 10 mx.example.org
example.org TXT v=spf1 +mx -all
example.org CAA 0 issue "letsencrypt.org"
0 issuewild "letsencrypt.org"
0 iodef "mailto:yourmail@example.jp"
_acme-challenge.example.org NS ns.example.org

§逆引きホスト名の設定

example.org

§初回SSH接続時の初期設定

§SSH接続する

ssh root@203.0.113.123

§SSH接続後のメッセージ(MOTD; Message Of The Day)を確認する

XServer で Ubuntu 26.04 LTS をインストールした場合は以下のようになりました。

Welcome to Ubuntu 26.04 LTS (GNU/Linux 7.0.0-14-generic x86_64)

 * Documentation:  https://docs.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/pro

 System information as of 2026年 6月 27日 土曜日 01:23:45 JST

  System load:  0.14               Processes:             165
  Usage of /:   1.2% of 386.85GB   Users logged in:       0
  Memory usage: 3%                 IPv4 address for ens3: 999.999.999.999
  Swap usage:   0%

 * Strictly confined Kubernetes makes edge and IoT secure. Learn how MicroK8s
   just raised the bar for easy, resilient and secure K8s cluster deployment.

   https://ubuntu.com/engage/secure-kubernetes-at-the-edge

Expanded Security Maintenance for Applicationsが無効化されています。

12のアップデートはすぐに適用されます。
これらの追加アップデートを確認するには次を実行してください: apt list --upgradable

ESM Appsを有効化すると、さらに追加のセキュリティアップデートを適用できます。
https://ubuntu.com/esm を参照するか、次のコマンドを実行してください: sudo pro status


*** システムの再起動が必要です ***
Last login: Sat Jun 27 01:23:45 2026 from 777.777.777.777

§ESM Apps を有効化する

Ubuntu ログイン時に以下のような表示がされることがあります。

Expanded Security Maintenance for Applications is not enabled.

0 updates can be applied immediately.

5 additional security updates can be applied with ESM Apps.
Learn more about enabling ESM Apps service at https://ubuntu.com/esm

ESM Apps (Expanded Security Maintenance for Applications) は Ubuntu Pro が提供しているセキュリティ管理の拡張機能です。Ubuntu Pro は通常有償ですが、Ubuntu ONE アカウントがあれば無償で最大5台まで LTS Ubuntu を対象にサポートを受けることができます。

  1. Ubuntu ONE のアカウントを作成する https://login.ubuntu.com/
  2. https://ubuntu.com/ にログインし、Ubuntu Pro dashboard https://ubuntu.com/pro/dashboard へアクセスする
  3. Ubuntu 上で次のコマンドを実行する sudo pro attach $FREE_PERSONAL_TOKEN
  4. apt を更新する sudo apt update, sudo apt upgrade
yourname@example:~$ sudo pro attach ***
[sudo] password for yourname:
Enabling Ubuntu Pro: ESM Apps
Ubuntu Pro: ESM Apps enabled
Enabling Ubuntu Pro: ESM Infra
Ubuntu Pro: ESM Infra enabled
Enabling Livepatch
Livepatch enabled
This machine is now attached to 'Ubuntu Pro - free personal subscription'

SERVICE          ENTITLED  STATUS       DESCRIPTION
anbox-cloud      yes       disabled     Scalable Android in the cloud
esm-apps         yes       enabled      Expanded Security Maintenance for Applications
esm-infra        yes       enabled      Expanded Security Maintenance for Infrastructure
landscape        yes       disabled     Management and administration tool for Ubuntu
livepatch        yes       enabled      Canonical Livepatch service
realtime-kernel* yes       disabled     Ubuntu kernel with PREEMPT_RT patches integrated

 * Service has variants

NOTICES
Operation in progress: pro attach

For a list of all Ubuntu Pro services and variants, run 'pro status --all'
Enable services with: pro enable <service>

     Account: yourmail@example.jp
Subscription: Ubuntu Pro - free personal subscription

ESM Apps が有効な状態であれば、Ubuntu ログイン時には次のように表示されます。

Expanded Security Maintenance for Applications is enabled.

0 updates can be applied immediately.

§rootパスワードを変更する

passwd

§apt 更新

sudo apt update
sudo apt list --upgradable
sudo apt upgrade
sudo reboot

§apt コマンド

#// パッケージ検索
apt search PACKAGE

#// インストール済みパッケージ検索
dpkg -l

#// インストール
apt -y install PACKAGE

#// アンインストール(purge は環境設定、auto-remove は依存パッケージ含む)
apt purge --auto-remove PACKAGE

§システムロケール

sudo apt -y install language-pack-ja manpages-ja
locale -a
C
C.utf8
POSIX
en_US.utf8
ja_JP.utf8
locale
LANG=ja_JP.UTF-8
LANGUAGE=
LC_CTYPE="ja_JP.UTF-8"
LC_NUMERIC="ja_JP.UTF-8"
LC_TIME="ja_JP.UTF-8"
LC_COLLATE="ja_JP.UTF-8"
LC_MONETARY="ja_JP.UTF-8"
LC_MESSAGES="ja_JP.UTF-8"
LC_PAPER="ja_JP.UTF-8"
LC_NAME="ja_JP.UTF-8"
LC_ADDRESS="ja_JP.UTF-8"
LC_TELEPHONE="ja_JP.UTF-8"
LC_MEASUREMENT="ja_JP.UTF-8"
LC_IDENTIFICATION="ja_JP.UTF-8"
LC_ALL=
sudo update-locale LANG=en_US.UTF-8
locale
LANG=en_US.UTF-8
LANGUAGE=
LC_CTYPE="en_US.UTF-8"
LC_NUMERIC="en_US.UTF-8"
LC_TIME="en_US.UTF-8"
LC_COLLATE="en_US.UTF-8"
LC_MONETARY="en_US.UTF-8"
LC_MESSAGES="en_US.UTF-8"
LC_PAPER="en_US.UTF-8"
LC_NAME="en_US.UTF-8"
LC_ADDRESS="en_US.UTF-8"
LC_TELEPHONE="en_US.UTF-8"
LC_MEASUREMENT="en_US.UTF-8"
LC_IDENTIFICATION="en_US.UTF-8"
LC_ALL=

一度ターミナルを抜けて、再接続してMOTDの表示言語を確認します。

§en_US.UTF-8 を設定しているのに日本語表示される場合

rm /var/lib/update-notifier/updates-available

apt update

§en_US.UTF-8 がない場合

/etc/locale.gen を開いて en_US.UTF-8 UTF-8 がコメントアウトされていればアンコメントします。

その後 sudo locale-gen を実行して locale -aen_US.utf8 が表示されるかを確認します。

§パスワード入力時の入力フィードバックを無効にする

一般ユーザーが sudo コマンドなどでパスワードを入力する際にアスタリスク * が表示されることがあります。

これを無効化するには、次のような設定ファイルを追加します。

sudo vi /etc/sudoers.d/no-pwfeedback
Defaults !pwfeedback

§OSユーザー作成

sudo adduser yourname
#// input password

sudo gpasswd -a yourname sudo
sudo gpasswd -a yourname adm
sudo cp -r /root/.ssh /home/yourname/.ssh
sudo chown -R yourname:yourname /home/yourname/.ssh
Host yourname
  HostName 203.0.113.123
  User yourname
  IdentityFile ~/.ssh/id_ed25519

§root ログインを禁止する

sudo 可能な一般ユーザーでログインできることが確認できたら、root ログインを禁止します。

sudo sshd -T | grep permitrootlogin
sudo emacs /etc/ssh/sshd_config.d/99-user.conf
PermitRootLogin no
#// 構文チェックをしておく
sudo sshd -t

sudo systemctl reload ssh
#// クライアントから root ログインできなくなる
ssh root@203.0.113.123```

# emacs 設定

```sh
sudo apt -y install emacs
mkdir ~/.emacs.d
curl -fsSL https://server.lavoscore.org/file/emacs/init.el -o ~/.emacs.d/init.el

#// 一度 Emacs を開く
emacs ~/.emacs.d/init.el
#// Emacs を抜ける
;; UTF-8 として扱う
;; prefer-coding-system は使わないことにする
;; https://github.com/takueof/.emacs.d/blob/master/init.el
; (prefer-coding-system 'utf-8)
; (setq coding-system-for-read 'utf-8)
; (setq coding-system-for-write 'utf-8)
(set-language-environment "Japanese")
(set-coding-system-priority 'utf-8)
(setq-default buffer-file-coding-system 'utf-8-unix)

(set-fontset-font t 'unicode "Noto Color Emoji" nil 'prepend)

;; emacs の自動バックアップによる ~ 付きのファイル生成を止める
(setq backup-inhibited t)
(setq delete-auto-save-files t)

;; インデントをソフトタブにする
(setq-default indent-tabs-mode nil)
(setq lisp-indent-offset 2)

;; シンボリックリンクの読み込みを許可
(setq vc-follow-symlinks t)
;; シンボリックリンク先のVCS内で更新が入った場合にバッファを自動更新
(setq auto-revert-check-vc-info t)

;; 空白類文字の可視化
(require 'whitespace)
(set-face-foreground 'whitespace-space "#ccc")
(set-face-background 'whitespace-space nil)
(set-face-bold-p 'whitespace-space t)
(set-face-foreground 'whitespace-tab "#333")
(set-face-background 'whitespace-tab nil)
(set-face-underline  'whitespace-tab t)
(setq whitespace-style '(face tabs tab-mark spaces space-mark))
(setq whitespace-space-regexp "\\(\x3000+\\)")
(setq whitespace-display-mappings
  '((space-mark ?\x3000 [?\□])
   (tab-mark ?\t [?\xBB ?\t])
   ))
(global-whitespace-mode 1) ;; 全角スペースを常に表示
(setq-default show-trailing-whitespace t) ;; 末尾のスペースを表示

;; list the packages you want
(setq package-list '(display-line-numbers))
;; fetch the list of packages available
;(unless package-archive-contents
;  (package-refresh-contents))
;; install the missing packages
(dolist (package package-list)
  (unless (package-installed-p package)
    (package-install package)))

(require 'display-line-numbers)
(global-display-line-numbers-mode t)
(setq display-line-numbers-format "%4d: ")
(column-number-mode t)
(line-number-mode t)

;; f6 で行番号表示をトグルする
;(global-set-key (kbd "<f6>") 'linum-mode)
(global-set-key (kbd "<f6>") 'display-line-numbers-mode)

;; [Ctrl + q] <tab> でハードタブを入力できるようにする
(global-set-key (kbd "<backtab>") (kbd "C-q <tab>"))

;; [Ctrl + z] で矩形選択編集を可能にする
(cua-mode t)
(setq cua-enable-cua-keys nil)
(global-set-key (kbd "C-z") 'cua-set-rectangle-mark)

;; M-x package-list-packages
;; M-x package-install PACKAGE_NAME
;; https://stackoverflow.com/questions/10092322/how-to-automatically-install-emacs-packages-by-specifying-a-list-of-package-name#10093312
;; list the repositories containing them
(setq package-archives '(("melpa-stable" . "https://stable.melpa.org/packages/")))
;; activate all the packages (in particular autoloads)
(package-initialize)

;; list the packages you want
(setq package-list '(editorconfig web-mode php-mode yaml-mode))
;; fetch the list of packages available
(unless package-archive-contents
  (package-refresh-contents))
;; install the missing packages
(dolist (package package-list)
  (unless (package-installed-p package)
    (package-install package)))

;; M-x package-install editorconfig
(require 'editorconfig)
(editorconfig-mode 1)

;; M-x package-install web-mode
(require 'web-mode)
(require 'php-mode)

(add-to-list 'auto-mode-alist '("\\.php\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.html\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.tsx?\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.svg\\'" . web-mode))

(add-to-list 'auto-mode-alist '("\\.ts\\'" . js-mode))
(add-to-list 'auto-mode-alist '("\\.mjs\\'" . js-mode))
(add-to-list 'auto-mode-alist '("\\.astro\\'" . web-mode))

(add-to-list 'auto-mode-alist '("\\.twig\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.yaml\\'" . yaml-mode))

;; web-mode で混在する言語のインデントを調整する
(defun my-web-mode-hook ()
  "Hooks for Web mode."
  (setq web-mode-attr-indent-offset 0)
  (setq web-mode-block-padding 0)
  )
(add-hook 'web-mode-hook 'my-web-mode-hook)

;; php-mode のインデントを調整する
(add-hook 'php-mode-hook
  (lambda ()
    (c-set-offset 'case-label' c-basic-offset)
    (c-set-offset 'arglist-intro' c-basic-offset)
    (c-set-offset 'arglist-cont-nonempty' c-basic-offset)
    (c-set-offset 'arglist-close' 0)
    ))

;; web-mode でのカラーリング
(custom-set-faces
 ;; custom-set-faces was added by Custom.
 ;; If you edit it by hand, you could mess it up, so be careful.
 ;; Your init file should contain only one such instance.
 ;; If there is more than one, they won't work right.
 '(web-mode-comment-face ((t (:foreground "#ff0000"))))
 '(web-mode-doctype-face ((t (:foreground "#808099"))))
 '(web-mode-function-call-face ((t (:foreground "#60ccff"))))
 '(web-mode-function-name-face ((t (:foreground "#60ccff"))))
 '(web-mode-html-attr-equal-face ((t (:foreground "#ffffff"))))
 '(web-mode-html-attr-name-face ((t (:foreground "#b07000"))))
 '(web-mode-html-attr-value-face ((t (:foreground "#ff0066"))))
 '(web-mode-html-tag-bracket-face ((t (:foreground "#ffffff"))))
 '(web-mode-html-tag-face ((t (:foreground "#60ccff")))))

(defun update-diff-colors ()
  "update the colors for diff faces"
  (set-face-attribute 'diff-header nil
    :foreground "white" :background "green")
  (set-face-attribute 'diff-file-header nil
    :foreground "white" :background "green")
  (set-face-attribute 'diff-added nil
    :foreground "white" :background "blue")
  (set-face-attribute 'diff-refine-added nil
    :foreground "white" :background "blue")
  (set-face-attribute 'diff-removed nil
    :foreground "white" :background "red")
  (set-face-attribute 'diff-refine-removed nil
    :foreground "white" :background "red")
  (set-face-attribute 'diff-changed nil
    :foreground "white" :background "purple")
  (set-face-attribute 'diff-refine-changed nil
    :foreground "white" :background "purple"))
(eval-after-load "diff-mode"
  '(update-diff-colors))

;; merge-conflict 時のカラーリング
(with-eval-after-load 'smerge-mode
  (set-face-attribute 'smerge-refined-added nil :background "#666")
  (set-face-attribute 'smerge-refined-removed nil :background "#666")
  (set-face-attribute 'smerge-upper nil :background "#000")
  (set-face-attribute 'smerge-lower nil :background "#000")
  (set-face-attribute 'smerge-base nil  :background "#000")
  (set-face-attribute 'smerge-markers nil :foreground "#000" :background "#ff0" :weight 'bold))
#// rootにも設置する
sudo -i
mkdir ~/.emacs.d
ln -s /home/yourname/.emacs.d/init.el ~/.emacs.d/

#// 一度 Emacs を開く
emacs ~/.emacs.d/init.el
#// Emacs を抜ける

#// root を抜ける
exit
sudo update-alternatives --config editor
#// 3 (emacs) など好きなエディタを選択する

§.editorconfig の設置

sudo curl -fsSL https://server.lavoscore.org/file/editorconfig.txt -o /.editorconfig
# 上位ディレクトリの EditorConfig を無視する場合は true
root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

[*.sh]
indent_style = space
indent_size = 2

[*.html]
indent_style = space
indent_size = 2

[*.svg]
indent_style = space
indent_size = 2

[*.php]
indent_style = space
indent_size = 2

[*.{css,scss}]
indent_style = space
indent_size = 2

[*.js]
indent_style = space
indent_size = 2

[*.mjs]
indent_style = space
indent_size = 2

[*.json]
indent_style = space
indent_size = 2

[*.yaml]
indent_style = space
indent_size = 2

[*.{astro,mdx}]
indent_style = space
indent_size = 2

§.profile 記述

emacs ~/.profile

#// ファイル末尾に以下の内容を追記する
# [dir => Cyan, ln => Yellow, or => Blink-Red, mi => Blink-Red]
# or = Orphaned symlink (symlinkFrom), mi = Missing file (symlinkTo; displayed by `ls -l`)
# refs: `dircolors` or `man dir_colors`
export LS_COLORS="${LS_COLORS}di=01;36:ln=01;33:or=01;05;37;41:mi=01;05;37;41:"

#   "\[\e]0;\u@\h: \w\a\]${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ "
PS1="\[\e]0;\u@\h: \w\a\]${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;35m\]\w\[\033[00m\]\$ "

PATH="$HOME/bin:$HOME/.local/bin:$PATH"

alias s='ls'
alias sl='ls'
alias qls='ls'
alias qll='ll'
alias qgit='git'
alias emasc='emacs'

# https://orangeclover.hatenablog.com/entry/20110201/1296511181
alias clear2="echo -e '\026\033c'"
#// 一度ターミナルを抜けて、入り直す
exit

§git 設定

emacs ~/.gitconfig
[user]
        name = yourname
        email = yourname@example.org
[color]
        diff = auto
        status = auto
        branch = auto
        interactive = auto
        ui = auto
[alias]
        co           = checkout
        st           = status
        di           = diff
        diw          = diff --color-words='[[:alnum:]]+|[^[:space:]]'
        push-reset   = push -f origin HEAD:master
        amend        = commit --amend
        pick         = cherry-pick
        dic          = diff --cached
        ls           = ls-files
        up-assume    = update-index --assume-unchanged
        up-no-assume = update-index --no-assume-unchanged
        up-skip      = update-index --skip-worktree
        up-no-skip   = update-index --no-skip-worktree
        clog         = log --all --color --graph --pretty=format:'%x09%C(yellow dim)%h%Creset %C(green dim)%cd%Creset %C(cyan dim)%an%Creset %s %C(red)%d%Creset' --date=format:'%Y/%m/%d %H:%M:%S'
[core]
        pager = less -+S
[push]
        default = simple
[safe]
        directory = /etc
[init]
        defaultBranch = main
#// root にも設置する
sudo ln -s /home/yourname/.gitconfig /root
cd /etc

sudo emacs .gitignore

#// 以下の内容を .gitignore ファイルに記述する
.gitignore
/letsencrypt/
/tinydns/
sudo git init .
sudo git add .
sudo git commit -m "first commit"

§/etc/hostname 設定

sudo emacs /etc/hostname
example
sudo reboot

§ファイアウォールを有効にする

sudo ufw status numbered
sudo ufw default deny
sudo ufw allow ssh
sudo ufw allow http
sudo ufw allow https
sudo ufw allow domain
sudo ufw enable

#// enable really? [y/n]

sudo ufw status numbered
cd /etc
sudo git add .
sudo git commit -m "ufw"

§ミドルウェア

sudo apt -y install apache2
sudo apt -y install mariadb-server
sudo apt -y install build-essential libssl-dev libreadline-dev zlib1g-dev libffi-dev libyaml-dev

#// Ubuntu 26.04 LTS では ppa:ondrej/php が未対応のため使えない
#// sudo add-apt-repository ppa:ondrej/php
sudo apt update

sudo apt -y install php8.5-fpm
#// php8-5-fpm dependencies: php8.5-cli php8.5-common php8.5-readline
#// php8.5-common including opcache
sudo apt -y install php8.5-bcmath php8.5-curl php8.5-gd php8.5-imagick php8.5-intl php8.5-mbstring php8.5-mysql php8.5-sqlite3 php8.5-xml php8.5-zip

sudo apt -y install ffmpeg imagemagick jq ripgrep tree zip
sudo apt list --upgradable
sudo apt upgrade

sudo reboot
cd /etc
sudo git add .
sudo git commit -m "middleware installed"
cd ~
emacs getcomposer.sh
#!/bin/sh

EXPECTED_CHECKSUM="$(php -r 'copy("https://composer.github.io/installer.sig", "php://stdout");')"
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
ACTUAL_CHECKSUM="$(php -r "echo hash_file('sha384', 'composer-setup.php');")"

if [ "$EXPECTED_CHECKSUM" != "$ACTUAL_CHECKSUM" ]
then
    >&2 echo 'ERROR: Invalid installer checksum'
    rm composer-setup.php
    exit 1
fi

php composer-setup.php --quiet
RESULT=$?
rm composer-setup.php
exit $RESULT
chmod +x getcomposer.sh
./getcomposer.sh
rm getcomposer.sh

mkdir ~/bin
mv composer.phar ~/bin/composer

§mise

cd ~
curl -fsSL https://mise.run -o mise-setup.sh
sh mise-setup.sh
rm mise-setup.sh
emacs ~/.profile
# mise
eval "$(~/.local/bin/mise activate bash)"
#// 一度ターミナルを抜けて、入り直す
exit
mise ls-remote ruby
mise latest ruby
mise ls

§Java

mise install java
mise use java

§Ruby

mise install ruby
mise use ruby
gem --version
gem update --system

§python

ln -s /usr/bin/python3 ~/bin/python

§go

mise install go
mise use go
go install github.com/sonots/lltsv@latest
lltsv --version

§node

mise install node
mise use node
npm --version
npm update -g npm

§php.ini の設定

sudo mkdir /etc/php/config
sudo emacs /etc/php/config/99-user.ini
memory_limit = 256M
error_reporting = E_ALL
display_errors = On
post_max_size = 64M
cgi.fix_pathinfo = 0
upload_max_filesize = 64M
max_file_uploads = 128
date.timezone = Asia/Tokyo
session.gc_maxlifetime = 86400
sudo ln -s /etc/php/config/99-user.ini /etc/php/8.5/fpm/conf.d/
sudo ln -s /etc/php/config/99-user.ini /etc/php/8.5/cli/conf.d/
cd /etc
sudo git add .
sudo git commit -m "php.ini"

§MySQL / MariaDB

§MariaDB を起動する

mariadb --version
systemctl status mariadb
sudo systemctl enable mariadb

§MySQL 一般ユーザーを作成する

sudo mariadb -u root
#// ユーザーを作成する
mysql> CREATE USER mysqluser@localhost IDENTIFIED by 'YOUR_DB_PASSWORD';

#// 権限を与える
mysql> GRANT ALL PRIVILEGES ON *.* TO mysqluser@localhost;

#// MySQL を抜ける
mysql> quit

§権限を外す場合

#// 権限を外す
mysql> REVOKE ALL PRIVILEGES ON *.* FROM mysqluser@localhost;

#// 特定の DB のみに権限を与える場合
#// この例では proj_ を接頭辞に持ち、その後ろに少なくとも 1 文字以上を持つ DB への権限が与えられる
#// LIKE 句と同様に、ワイルドカード _ は任意の1文字であり % は任意の 0 文字以上の意味を持つ
mysql> GRANT ALL PRIVILEGES ON `proj\__%`.* TO mysqluser@localhost;

§存在するユーザーの権限確認

mysql> SELECT Host, User FROM mysql.user;
mysql> SELECT * FROM mysql.user\G
mysql> SELECT * FROM mysql.db\G

§設定ファイルを用いて mysql を使えるようにする

emacs ~/.my.cnf
[client]
host = localhost
user = mysqluser
password = YOUR_DB_PASSWORD
mysql --defaults-extra-file=~/.another.cnf

§全データベースのバックアップとリストア

mysqldump --single-transaction --routines --events --triggers --all-databases > dbdump.sql

mysqldump --single-transaction --routines --events --triggers --databases $(mysql -BNe "SHOW DATABASES;" | grep -vxE 'information_schema|mysql|performance_schema|sys') > dbdump.sql
mysql < dbdump.sql

§文字コード

mysql> show variables like 'char%';
+--------------------------+----------------------------+
| Variable_name            | Value                      |
+--------------------------+----------------------------+
| character_set_client     | utf8mb4                    |
| character_set_connection | utf8mb4                    |
| character_set_database   | utf8mb4                    |
| character_set_filesystem | binary                     |
| character_set_results    | utf8mb4                    |
| character_set_server     | utf8mb4                    |
| character_set_system     | utf8mb3                    |
| character_sets_dir       | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+

§Web 公開用ディレクトリを作成する

#// gweb グループを作成
#// 自身を gweb に追加
#// www-data (Apache) を gweb に追加(www-data は envvar で設定されている)

sudo groupadd gweb
sudo gpasswd -a yourname gweb
sudo gpasswd -a www-data gweb
#// 一度ターミナルを抜けて、入り直す
exit
cd /var/www/
sudo mkdir hosts
cd hosts/

#// hosts/ のグループを gweb に変更する
#// グループに書き込み権限を与える
#// グループに setgid を与える

sudo chgrp gweb .
sudo chmod g+w .
sudo chmod g+s .
mkdir web
cd /var/www/hosts/web/
mkdir -p www/public_html
emacs www/public_html/index.html

#// hello とでも打って保存しておく

§Apache の設定

§モジュールの有効化

sudo apache2ctl -M
sudo a2enmod rewrite
sudo a2enmod vhost_alias
sudo a2enmod authz_groupfile
sudo a2enmod headers
sudo a2enmod include
sudo a2enmod expires
sudo a2enmod ssl
sudo a2enmod cgi
sudo a2enmod proxy
sudo a2enmod proxy_fcgi

§security.conf の設定

sudo emacs /etc/apache2/conf-available/security.conf
 # Set to one of:  Full | OS | Minimal | Minor | Major | Prod
 # where Full conveys the most information, and Prod the least.
 #ServerTokens Minimal
-ServerTokens OS
+#ServerTokens OS
 #ServerTokens Full
+ServerTokens Prod

 # Set to "EMail" to also include a mailto: link to the ServerAdmin.
 # Set to one of:  On | Off | EMail
-#ServerSignature Off
-ServerSignature On
+ServerSignature Off
+#ServerSignature On

§dir.conf の設定

sudo emacs /etc/apache2/mods-enabled/dir.conf
-DirectoryIndex index.html index.cgi index.pl index.php index.xhtml index.htm
+DirectoryIndex index.php index.cgi index.rb index.html

§envvars の設定

sudo emacs /etc/apache2/envvars
-export LANG=C.UTF-8
+# export LANG=C.UTF-8
+export LANG=ja_JP.UTF-8
 # Uncomment the following line to use the system default locale instead:
 #. /etc/default/locale

 export LANG
# CUSTOMIZE: set 002 to permit g+w for setgid directory
umask 002

§httpd.conf の設定をする - HTTP/HTTPS

sudo apt -y install cronolog

§includes 用ファイルを作成する

cd /etc/apache2/
sudo mkdir includes
cd includes/
sudo emacs make_includes.sh
#!/bin/bash

DOMAIN_APEX_LIST=(
    example.org
)

echo '  # Enable this if your want HSTS (recommended)
  # * 15768000 = 60 * 60 * 24 * 365 / 2 (= 0.5 year)
  Header always set Strict-Transport-Security "max-age=15768000"' > hsts.conf;

echo '  # Enable this if your want HSTS (recommended)
  # * 15768000 = 60 * 60 * 24 * 365 / 2 (= 0.5 year)
  Header always set Strict-Transport-Security "max-age=0"' > hsts_zero.conf;

echo '  <Directory /var/www/hosts/web>
    RewriteEngine On
    RewriteCond %{HTTPS} off
    RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R,L]
  </Directory>' > http2https.conf

echo '  UseCanonicalName Off' > http.conf

for DOMAIN_APEX in ${DOMAIN_APEX_LIST[@]}; do
    echo "  UseCanonicalName Off
  Protocols h2 http/1.1
  # refs. https://wiki.mozilla.org/Security/Server_Side_TLS
  # refs. https://mozilla.github.io/server-side-tls/ssl-config-generator/
  SSLEngine on
  # SSLCertificateFile    /etc/ssl/certs/ssl-cert-snakeoil.pem
  # SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
  SSLCertificateFile      /etc/ssl/hosts/${DOMAIN_APEX}/cert.pem
  SSLCertificateKeyFile   /etc/ssl/hosts/${DOMAIN_APEX}/privkey.pem
  SSLCertificateChainFile /etc/ssl/hosts/${DOMAIN_APEX}/chain.pem
  # Intermediate configuration, tweak to your needs
  SSLProtocol All -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
  # SSLCipherSuite FIPS@STRENGTH:!aNULL:!eNULL
  SSLCipherSuite ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256
  SSLHonorCipherOrder on
  SSLCompression off
  # 'SSLSessionTickets' directive (Available in httpd 2.4.11 and later)
  SSLSessionTickets off
  # OCSP Stapling, only in httpd 2.3.3 and later
  SSLUseStapling on
  SSLStaplingResponderTimeout 5
  SSLStaplingReturnResponderErrors off
  # Enable this if your want HSTS (recommended)
  # * 15768000 = 60 * 60 * 24 * 365 / 2 (= 0.5 year)
  # Header always set Strict-Transport-Security 'max-age=15768000'
  # * To disable HSTS. We are using not only HTTPS but HTTP." > "https_${DOMAIN_APEX}.conf"
done

echo '  LogLevel warn
  # ErrorLog ${APACHE_LOG_DIR}/error.log
  # CustomLog ${APACHE_LOG_DIR}/access.log ltsv
  ErrorLog "| /usr/bin/cronolog /var/log/apache2/logs/error_%Y_%m.log"
  CustomLog "| /usr/bin/cronolog /var/log/apache2/logs/access_%Y_%m.log" ltsv
  LogFormat "host:%h\ttime:%t\treq:%r\tstatus:%>s\tsize:%b\treferer:%{Referer}i\tua:%{User-Agent}i\ttaken:%D\tisbot:%{Isbot}e\tdos:%{SuspectDoS}e\tharddos:%{SuspectHardDoS}ecache:%{X-Cache}o" ltsv_legacy
  LogFormat "opt_time:%{%Y/%m/%d %H:%M:%S}t\tserver:%A\tdomain:%V\tpath:%U%q\thttps:%{HTTPS}e\tmethod:%m\tstatus:%>s\tprotocol:%H\tuser:%u\tident:%l\tsize:%b\tresponse_time:%D\tcookie:%{cookie}i\tset_cookie:%{Set-Cookie}o\ttime:%{%d/%b/%Y:%H:%M:%S %z}t\treferer:%{Referer}i\tagent:%{User-Agent}i\thost:%h" ltsv' > log.conf
sudo chmod +x make_includes.sh
sudo ./make_includes.sh

§httpd.conf を記述する

cd /etc/apache2/

sudo a2dissite 000-default
sudo emacs /etc/apache2/sites-available/001-original.conf
SSLStrictSNIVHostCheck off

############################################################
# default rule (for IP or default host request)
############################################################
<VirtualHost *:80>
  ServerName 203.0.113.123
  ServerAlias host-203-0-113-123.vps.example.ne.jp
  DocumentRoot /var/www/hosts/web/www/public_html
  Include includes/http.conf
  Include includes/log.conf
  <Location />
    Require all denied
  </Location>
</VirtualHost>

<VirtualHost *:443>
  ServerName 203.0.113.123
  ServerAlias host-203-0-113-123.vps.example.ne.jp
  DocumentRoot /var/www/hosts/web/www/public_html
  Include includes/https_example.org.conf
  Include includes/log.conf
  <Location />
    Require all denied
  </Location>
</VirtualHost>

############################################################
# ssl.example.org
############################################################
<VirtualHost *:80>
  ServerName ssl.example.org
  VirtualDocumentRoot /var/www/hosts/web/%-3+/public_html
  Redirect / https://ssl.example.org/
  Include includes/http.conf
  Include includes/log.conf
</VirtualHost>

<VirtualHost *:443>
  ServerName ssl.example.org
  VirtualDocumentRoot /var/www/hosts/web/%-3+/public_html
  Include includes/hsts.conf
  Include includes/https_example.org.conf
  Include includes/log.conf
</VirtualHost>

############################################################
# www.example.org, *.example.org
############################################################
<VirtualHost *:80>
  ServerName www.example.org
  ServerAlias *.example.org
  VirtualDocumentRoot /var/www/hosts/web/%-3+/public_html
  Include includes/http.conf
  Include includes/log.conf
</VirtualHost>

<VirtualHost *:443>
  ServerName www.example.org
  ServerAlias *.example.org
  VirtualDocumentRoot /var/www/hosts/web/%-3+/public_html
  Include includes/https_example.org.conf
  Include includes/log.conf
</VirtualHost>

############################################################
# example.org
############################################################
<VirtualHost *:80>
  ServerName example.org
  VirtualDocumentRoot /var/www/hosts/web/www/public_html
  Include includes/http.conf
  Include includes/log.conf
</VirtualHost>

<VirtualHost *:443>
  ServerName example.org
  VirtualDocumentRoot /var/www/hosts/web/www/public_html
  Include includes/https_example.org.conf
  Include includes/log.conf
</VirtualHost>

############################################################
# others
############################################################

# SSLStaplingCache
SSLStaplingCache shmcb:/var/run/ocsp(128000)

# for JavaScript
AddType application/javascript .js
AddCharset UTF-8 .js
# for CSS
AddType text/css .css
AddCharset UTF-8 .css

# Access denied to all directories (/var/www)
<Directory /var/www>
  Options FollowSymLinks
  AllowOverride None
  Require all denied
  # Access denied to dot-files such as .git/*, but excepts .well-known/*
  RedirectMatch 403 /\.(?!well-known/)
</Directory>

# Access granted to hosts directory (/var/www/hosts)
<Directory /var/www/hosts>
  Options FollowSymLinks
  AllowOverride All
  Require all granted
  # 403 Forbidden for non-existing sub-domains.
  RewriteEngine On
  RewriteCond %{DOCUMENT_ROOT} !-d
  RewriteRule ^ - [F]

  <IfModule proxy_fcgi_module>
    <FilesMatch "\.ph(?:ar|p|tml)$">
      <If "-f %{REQUEST_FILENAME}">
        SetHandler "proxy:unix:/run/php/php8.5-fpm.sock|fcgi://localhost"
      </If>
    </FilesMatch>
  </IfModule>
</Directory>
sudo a2ensite 001-original
sudo apache2ctl configtest
#// SSL サーバー証明書を用意していないのでここではエラーになる
cd /etc
sudo git add .
sudo git commit -m "apache"

§.htaccess への記述

# authAccess
#   NOTE: Execute the following command to create .htpasswd
#     $ htpasswd -c .htpasswd username
AuthUserFile /var/www/hosts/web/DIR_NAME/.htpasswd
AuthName "Authorization Required"
AuthType Basic
Require valid-user
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R,L]

§Let's Encrypt 証明書と tinydns の導入

§certbot インストール

sudo apt -y install snapd

sudo snap install --classic certbot

type certbot
certbot --version
cd /etc
sudo git add .
sudo git commit -m "certbot"

§tinydns インストール

sudo apt -y install djbdns-conf tinydns
sudo apt -y install svtools daemontools-run

sudo useradd tinydns
sudo useradd dnslog

sudo tinydns-conf tinydns dnslog /etc/tinydns $(hostname -I | grep -oE '\b([0-9]{1,3}\.){3}[0-9]{1,3}\b')

sudo ln -s /etc/tinydns /etc/service
sleep 5

sudo svstat /etc/service/tinydns

cd /etc/service/tinydns/root
sudo emacs data
._acme-challenge.example.org::ns.example.org:60
'_acme-challenge.example.org:tinydns-install-succeeded:60
sudo make

dig _acme-challenge.example.org txt +norec @$(hostname -I | grep -oE '\b([0-9]{1,3}\.){3}[0-9]{1,3}\b')
cd /etc
sudo git add .
sudo git commit -m "tinydns"

§Let's Encrypt 自動更新

cd /etc/ssl

sudo emacs make_scripts.sh
#/bin/sh

if [ $# -ne 2 ]; then
    echo "$0 example.org yourmail@example.jp"
    exit 1
fi

echo '#!/bin/sh -eu

certbot renew' >  will_update_letsencrypt.sh

echo '#!/bin/bash -eu

DOMAIN_APEX_LIST=(
    '"$1"'
)
MAIL_ADDRESS="'"$2"'"

SCRIPT_DIR="/etc/ssl"
AUTH_HOOK_PATH="${SCRIPT_DIR}/certbot-auth-hook.sh"
CLEANUP_HOOK_PATH="${SCRIPT_DIR}/certbot-cleanup-hook.sh"

for DOMAIN_APEX in ${DOMAIN_APEX_LIST[@]}; do
    certbot certonly --agree-tos --manual --preferred-challenges dns-01 --force-renewal \
        -d "*.${DOMAIN_APEX}" -d "$DOMAIN_APEX" -m "$MAIL_ADDRESS" \
        --manual-auth-hook "$AUTH_HOOK_PATH" --manual-cleanup-hook "$CLEANUP_HOOK_PATH"
done' > update_letsencrypt.sh

echo '#!/bin/sh

systemctl restart php8.5-fpm
service apache2 restart
service postfix restart' > restart_ssl_services.sh

echo '#!/bin/sh

/etc/ssl/update_letsencrypt.sh
/etc/ssl/restart_ssl_services.sh' > update_letsencrypt_and_postprocess.sh

echo '#!/bin/sh

DATA_PARENT_DIR="/etc/service/tinydns/root"
DATA_PATH="${DATA_PARENT_DIR}/data"

DOMAIN="_acme-challenge.${CERTBOT_DOMAIN}"

TXT_RECORD="'\''${DOMAIN}:${CERTBOT_VALIDATION}:60"

echo "${TXT_RECORD}" >> ${DATA_PATH}
make -C ${DATA_PARENT_DIR}
sleep 1' > certbot-auth-hook.sh

echo '#!/bin/sh

DATA_PARENT_DIR="/etc/service/tinydns/root"
DATA_PATH="${DATA_PARENT_DIR}/data"

DOMAIN="_acme-challenge.${CERTBOT_DOMAIN}"

TARGET_RECORD="'\''${DOMAIN}:${CERTBOT_VALIDATION}:"

sed -i.bak -e "/${TARGET_RECORD}/d" ${DATA_PATH}
make -C ${DATA_PARENT_DIR}' > certbot-cleanup-hook.sh

chmod +x *.sh
sudo chmod +x make_scripts.sh
#// ドメイン名と通常利用のメールアドレスを引数にする
sudo ./make_scripts.sh example.org yourmail@example.jp
cd /etc
sudo git add .
sudo git commit -m "/etc/ssl"

§cron 記述とスクリプト作成

§/etc/crontab

sudo emacs /etc/crontab
# Update sslcert (Let's Encrypt)
10 6    1 * *    root    /etc/ssl/update_letsencrypt_and_postprocess.sh
cd /etc
sudo git add .
sudo git commit -m "crontab"

§証明書作成 DNS-01 チャレンジ

cd /etc/ssl/

sudo ./update_letsencrypt.sh
sudo ln -nfs /etc/letsencrypt/live /etc/ssl/hosts
sudo apache2ctl configtest

sudo service apache2 restart
cd /etc
sudo git add .
sudo git commit -m "letsencrypt"

§証明書作成 HTTP-01 チャレンジ

sudo certbot certonly --apache -d foo.example.org -d bar.example.org

§証明書失効

sudo mkdir /etc/ssl/ocsp-stapling
sudo cp -r /etc/letsencrypt/archive/foo.example.org /etc/ssl/ocsp-stapling
sudo certbot revoke --cert-path /etc/letsencrypt/live/foo.example.org/cert.pem

§OS のパスワードポリシーを設定する

cat /etc/pam.d/common-password
# here are the per-package modules (the "Primary" block)
password        [success=1 default=ignore]        pam_unix.so obscure yescrypt
# here's the fallback if no module succeeds
password        requisite                        pam_deny.so
# prime the stack with a positive return value if there isn't one already;
# this avoids us returning an error just because nothing sets a success code
# since the modules above will each just jump around
password        required                        pam_permit.so
# and here are more per-package modules (the "Additional" block)
# end of pam-auth-update config
sudo apt -y install libpam-pwquality
 # here are the per-package modules (the "Primary" block)
-password       [success=1 default=ignore]      pam_unix.so obscure yescrypt
+password       requisite                       pam_pwquality.so retry=3
+password       [success=1 default=ignore]      pam_unix.so obscure use_authtok try_first_pass yescrypt

この状態でも辞書にあるフレーズが設定できないなど多少は設定できるパスワードを制限できますが、このままでは最低4文字のパスワードを設定することが可能なので制限を厳しくしておきます。

sudo emacs /etc/pam.d/common-password
 # here are the per-package modules (the "Primary" block)
-password       requisite                       pam_pwquality.so retry=3
+password       requisite                       pam_pwquality.so retry=3 minlen=10 difok=1 dcredit=0 ucredit=0 lcredit=0 ocredit=0
 password       [success=1 default=ignore]      pam_unix.so obscure use_authtok try_first_pass sha512
cd /etc
sudo git add .
sudo git commit -m "common-password"

§postfix をインストールする

sudo apt -y install postfix
#// Internet Site を選ぶ
cd /etc
sudo git add .
sudo git commit -m "postfix"
sudo emacs /etc/postfix/main.cf
-smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
-smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
+#smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
+#smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
+smtpd_tls_cert_file=/etc/ssl/hosts/example.org/cert.pem
+smtpd_tls_key_file=/etc/ssl/hosts/example.org/privkey.pem
# Rewrite Envelope from
sender_canonical_classes = envelope_sender
sender_canonical_maps = regexp:/etc/postfix/sender_canonical.regexp
local_header_rewrite_clients = static:all
# CatchAll
local_recipient_maps =
luser_relay = yourname
sudo emacs /etc/postfix/sender_canonical.regexp
/^(.*)@(.+)$/ $1@example.org
sudo service postfix restart
cd /etc
sudo git add .
sudo git commit -m "update postfix"
sudo emacs /etc/aliases
# See man 5 aliases for format
postmaster:    root
yourname:    yourmail@example.jp
cd /etc
sudo newaliases
sudo ufw allow smtp
sudo ufw allow smtps
sudo ufw reload
sudo service postfix restart
cd /etc
sudo git add .
sudo git commit -m "aliases"

§ドメイン名を追加する

以下は example.net を追加で使えるようにする場合の手順です。

§DNS RRs

host type value
_acme-challenge.example.net NS ns.example.net
ns.example.net A 203.0.113.123

§tinydns

cd /etc/service/tinydns/root
sudo emacs data
#// 以下を追記する
._acme-challenge.example.net::ns.example.net:60
'_acme-challenge.example.net:tinydns-install-succeeded:60
sudo make

§/etc/ssl

sudo emacs /etc/ssl/update_letsencrypt.sh
 DOMAIN_APEX_LIST=(
     example.org
+    example.net
 )
sudo /etc/ssl/update_letsencrypt.sh

§Apache

cd /etc/apache2/includes
sudo emacs make_includes.sh
 DOMAIN_APEX_LIST=(
     example.org
+    example.net
 )
sudo ./make_includes.sh
sudo emacs /etc/apache2/sites-enabled/001-original.conf
############################################################
# www.example.net, *.example.net
############################################################
<VirtualHost *:80>
  ServerName www.example.net
  ServerAlias *.example.net
  VirtualDocumentRoot /var/www/hosts/example.net/%-3+/public_html
  Include includes/http.conf
  Include includes/log.conf
</VirtualHost>

<VirtualHost *:443>
  ServerName www.example.net
  ServerAlias *.example.net
  VirtualDocumentRoot /var/www/hosts/example.net/%-3+/public_html
  Include includes/https_example.net.conf
  Include includes/log.conf
</VirtualHost>

############################################################
# example.net
############################################################
<VirtualHost *:80>
  ServerName example.net
  VirtualDocumentRoot /var/www/hosts/example.net/www/public_html
  Include includes/http.conf
  Include includes/log.conf
</VirtualHost>

<VirtualHost *:443>
  ServerName example.net
  VirtualDocumentRoot /var/www/hosts/example.net/www/public_html
  Include includes/https_example.net.conf
  Include includes/log.conf
</VirtualHost>
sudo apache2ctl configtest

sudo /etc/ssl/restart_ssl_services.sh

§既存サーバーのデータを取得する

§~/.my.cnf ファイルを作成する

emacs ~/.my.cnf
[client]
host = localhost
user = mysqluser
password = YOUR_DB_PASSWORD
mysql --defaults-extra-file=~/.another.cnf

§旧サーバーのストレージデータをローカルに保存する

cd ~

mysqldump --single-transaction --routines --events --triggers --databases $(mysql -BNe "SHOW DATABASES;" | grep -vxE 'information_schema|mysql|performance_schema|sys') > dbdump.sql
sudo dpkg --get-selections > dpkg.txt
sudo emacs /etc/sudoers.d/rsync-backup
yourname ALL=(root) NOPASSWD: /usr/bin/rsync
sudo chmod 440 /etc/sudoers.d/rsync-backup
ssh yourname@203.0.113.123 sudo rsync --version
rsync -aH --partial --info=progress2,stats --numeric-ids --rsync-path="sudo rsync" --exclude=/proc --exclude=/sys --exclude=/dev --exclude=/run --exclude=/tmp --exclude=/swapfile --exclude=/lost+found --exclude=/media --exclude=/mnt yourname@203.0.113.123:/ ~/path/to/LOCALDIR