7月31日に立てたMastodon作成ロードマップの最後である、自宅サーバーでMastodonをセルフホスティングするのを達成したので、そのログをここに記す。
Ubuntu実機AMD64環境でDockerを使わず、IPv6シングルスタックで構成している。
自宅サーバーを使ってIPv6シングルスタックで通信を待ち受ける方法はOCNバーチャルコネクト環境でIPv6サーバーをポート443で公開した時にやったことの方に書いている。
- 確認環境
- 前提条件
- 事前準備
- 依存関係のインストールとセットアップ
- Mastodon本体のセットアップ
- なんやかんやの末に立ち上がったMastodonインスタンス
- トラブルシューティング
- Dockerを使わなかった理由
- 実際に導入して驚いた部分
- 今後やっていきたい部分
- あとがき:OSグローバルのRubyを消してみた
確認環境
Ubuntu実機AMD64環境
| Env | Ver |
|---|---|
| Ubuntu | 24.04.3 LTS |
| nginx | 1.26.1 |
| Mastodon | v4.4.3 |
| Ruby | 3.2.3 |
| ruby-build | 20250811 |
| rbenv | 1.3.2-10-gd1a19a3 |
前提条件
- 既にnginxを運用しており、IPv6シングルスタックで公開できている
- certbotを使ったLet's EncryptでのTLS証明書発行が可能
事前準備
- 公開用のドメインを検討し、生み出す
- Value Domainのコンパネから生み出したドメインをAAAAレコードで登録
- 拙作の@lycolia/value-domain-dns-cert-registerを使ってTLS証明書を発行
依存関係のインストールとセットアップ
以下のコマンドを流す
sudo apt update sudo apt install -y \ imagemagick ffmpeg libvips-tools libpq-dev libxml2-dev libxslt1-dev file git-core \ g++ libprotobuf-dev protobuf-compiler pkg-config gcc autoconf \ bison build-essential libssl-dev libyaml-dev libreadline6-dev \ zlib1g-dev libncurses5-dev libffi-dev libgdbm-dev \ nginx redis-server redis-tools postgresql \ certbot python3-certbot-nginx libidn11-dev libicu-dev libjemalloc-dev \ # 最新版を入れないとうまくいかない wget https://github.com/rbenv/ruby-build/archive/refs/tags/v20250811.tar.gz tar -xzf v20250811.tar.gz sudo PREFIX=/usr/local ./ruby-build-20250811/install.sh rm v20250811.tar.gz rm -Rf ruby-build-20250811 # aptではサポート切れの化石が入るのとnvmで入れたのは自分しか使えないので新しいのをグローバルに入れる curl -fsSL https://deb.nodesource.com/setup_24.x -o nodesource_setup.sh sudo bash nodesource_setup.sh apt install -y nodejs rm nodesource_setup.sh corepack enable # メンテナンスでめっちゃ使うのでサービスユーザーにはせず、ログイン可能なユーザーとして作る adduser --disabled-password mastodon/etc/redis/redis.confを開きrequirepassのコメントを外し、パスワードを設定sudo service redis restartsudo -u postgres psqlでポスグレに入り、ユーザーを作る。ポスグレは不慣れで同時にPW設定する方法が判らなかったので別建てで設定しているCREATE USER mastodon CREATEDB; ALTER ROLE mastodon WITH PASSWORD 'パスワード'; \q
Mastodon本体のセットアップ
実際にはかなり行ったり来たりしてるので一部の手順には誤りが含まれている可能性がある。
mastodonユーザーにスイッチしてインストール作業を続行
# Mastodon本体の導入 sudo su - mastodon git clone https://github.com/mastodon/mastodon.git live # 超重要、cd忘れててもなんかそれっぽく進むが、以後すべて破綻する cd live git checkout $(git tag -l | grep '^v[0-9.]*$' | sort -V | tail -n 1) git clone https://github.com/rbenv/rbenv.git ~/.rbenv echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc echo 'eval "$(rbenv init -)"' >> ~/.bashrc exec bash # 動かないやつなのでコメントアウト # 依存関係のインストールとセットアップでやった内容をこっちでやってもいいかもしれない # git clone https://github.com/rbenv/ruby-build.git "$(rbenv root)"/plugins/ruby-build # ここでエラーが出たら何かが間違っているのでやり直し RUBY_CONFIGURE_OPTS=--with-jemalloc rbenv install # bundleがないとか、rubyのバージョンが存在しないとかエラーが出たら何かが間違っているのでやり直し bundle config deployment 'true' bundle config without 'development test' bundle install -j$(getconf _NPROCESSORS_ONLN) yarn install # 色々尋ねられるので適当に進める RAILS_ENV=production bin/rails mastodon:setupデーモン周りの設定をする
# 静的ファイルをnginxに見させるために権限を設定 sudo chmod o+x /home/mastodon # デーモンの登録と起動 sudo cp /home/mastodon/live/dist/mastodon-*.service /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable --now mastodon-web mastodon-sidekiq mastodon-streaming sudo service mastodon-web start sudo service mastodon-sidekiq start sudo service mastodon-streaming startnginxの設定を書く
map $http_upgrade $connection_upgrade { default upgrade; '' close; } upstream backend { server 127.0.0.1:3000 fail_timeout=0; } upstream streaming { # Instruct nginx to send connections to the server with the least number of connections # to ensure load is distributed evenly. least_conn; server 127.0.0.1:4000 fail_timeout=0; # Uncomment these lines for load-balancing multiple instances of streaming for scaling, # this assumes your running the streaming server on ports 4000, 4001, and 4002: # server 127.0.0.1:4001 fail_timeout=0; # server 127.0.0.1:4002 fail_timeout=0; } proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=CACHE:10m inactive=7d max_size=1g; server { listen [::]:443 ssl http2; server_name <ドメイン>; ssl_certificate /etc/letsencrypt/live/<証明書のパス>/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/<証明書のパス>/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; # You can use https://ssl-config.mozilla.org/ to generate your cipher set. # We recommend their "Intermediate" level. ssl_ciphers CDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA84:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA38:DHE-RSA-CHACHA20-POLY1305; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; ssl_session_tickets off; keepalive_timeout 70; sendfile on; client_max_body_size 99m; root /home/mastodon/live/public; gzip on; gzip_disable "msie6"; gzip_vary on; gzip_proxied any; gzip_comp_level 6; gzip_buffers 16 8k; gzip_http_version 1.1; gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xmlrss text/javascript image/svg+xml image/x-icon; gzip_static on; location / { try_files $uri @proxy; } # If Docker is used for deployment and Rails serves static files, # then needed must replace line `try_files $uri =404;` with `try_files $uri @proxy;`. location = /sw.js { add_header Cache-Control "public, max-age=604800, must-revalidate"; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; try_files $uri =404; } location ~ ^/assets/ { add_header Cache-Control "public, max-age=2419200, must-revalidate"; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; try_files $uri =404; } location ~ ^/avatars/ { add_header Cache-Control "public, max-age=2419200, must-revalidate"; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; try_files $uri =404; } location ~ ^/emoji/ { add_header Cache-Control "public, max-age=2419200, must-revalidate"; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; try_files $uri =404; } location ~ ^/headers/ { add_header Cache-Control "public, max-age=2419200, must-revalidate"; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; try_files $uri =404; } location ~ ^/packs/ { add_header Cache-Control "public, max-age=2419200, must-revalidate"; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; try_files $uri =404; } location ~ ^/shortcuts/ { add_header Cache-Control "public, max-age=2419200, must-revalidate"; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; try_files $uri =404; } location ~ ^/sounds/ { add_header Cache-Control "public, max-age=2419200, must-revalidate"; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; try_files $uri =404; } location ~ ^/system/ { add_header Cache-Control "public, max-age=2419200, immutable"; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; add_header X-Content-Type-Options nosniff; add_header Content-Security-Policy "default-src 'none'; form-action 'none'"; try_files $uri =404; } location ^~ /api/v1/streaming { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Proxy ""; proxy_pass http://streaming; proxy_buffering off; proxy_redirect off; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; tcp_nodelay on; } location @proxy { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Proxy ""; proxy_pass_header Server; proxy_pass http://backend; proxy_buffering on; proxy_redirect off; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_cache CACHE; proxy_cache_valid 200 7d; proxy_cache_valid 410 24h; proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504; add_header X-Cached $upstream_cache_status; tcp_nodelay on; } error_page 404 500 501 502 503 504 /500.html; }
なんやかんやの末に立ち上がったMastodonインスタンス
なんやかんやあり色々苦戦したが、無事立ち上がってくれて何よりだった。
立ち上がった直後。
そして現在。こうやって動いているのを見ると感慨深い。
実は中学生のころから自宅サーバーを立てるという夢を持っており、こうやって実際に公開できたことは感慨深い。実際のところ、実験用の小規模サーバーや、SHOUTcastサーバーだとか、シンクライアント用のSSHサーバーみたいなのは作った経験があり、いずれも外に出していたのでサーバーを作った経験が全くない、というわけではないのだが、監視環境を整えたり、IPv4が実用困難な環境で、IPv6に挑戦してみたりと、ここまで本格的にやったことはなかったのもあり、個人的には結構達成感がある。
トラブルシューティング
Mastdonのlistenしているポートを変更する
3000番はGrafanaをはじめ、様々なNode.js系のポートと干渉するため変えておく。
/etc/systemd/system/mastodon-web.serviceのEnvironment="PORT=3000"を変えることでWeb側のポートが変えられる/etc/systemd/system/mastodon-streaming.serviceのWants=mastodon-streaming@4000.serviceの4000の部分を変えることで、Streaming側のポートも変えられる- サービスを再起動する
sudo systemctl daemon-reload sudo service mastodon-web restart sudo service mastodon-streaming restart - nginxの設定も対応する箇所を変える
- nginxを再起動する
sudo service nginx restart
Mastodonのエラーページが表示される・Rubyがないとか出るなどセットアップ時のエラー全般
基本的にどこかの手順が間違ってるのでしつこくやり直すことで改善する。
投稿可能な文字数を増やす
以下の内容で修正すると増やせる。
app/validators/status_length_validator.rb
class StatusLengthValidator < ActiveModel::Validator
- MAX_CHARS = 500
+ MAX_CHARS = 50000
app/javascript/mastodon/features/compose/containers/compose_form_container.js
正直このファイルは直さなくても動くし、直しても、ほぼ意味がなさそうなので、直す必要はないかもしれない。
- maxChars: state.getIn(['server', 'server', 'configuration', 'statuses', 'max_characters'], 500),
+ maxChars: state.getIn(['server', 'server', 'configuration', 'statuses', 'max_characters'], 50000),
備考
変更直後は投稿画面の初期表示に500と出ていて、数文字打つと50000文字に変化する状態だったが、一日くらい経過すると初期値が50000に変わっていたのでキャッシュか何かが悪さをしていた可能性がある。
フォローできるが、フォロー一覧にもTLにも出てこないユーザーが出る
IPv6と疎通してないところは仕方がないね。AAAAレコード持ちでcurl -6 -v https://example.comしても疎通する場所でも起きることがあるが原因は不明。
兵庫丼でも偶に遭遇してたので、そういうものだと割り切るしかない。IPv4滅ぶべし、慈悲はない。
「制限に達しました1:35:00 以降に再度実行してください。」とエラーが出る
APIのレートリミットにかかっているせいっぽい。5分間に300リクエストまでと、かなり渋い。信用するIPを指定することで回避可能らしい。
.env.productionを開き、前述の資料に書かれている信頼できるIPを列挙した後に、自分のIPv6プレフィクス(aaaa:bbbb:cccc:dddd::/64を自分のプレフィクスに置き換え)を追加し、再起動しておいた。これで緩和されると良いのだが、出先では使えないと思うのでどうするか…。
TRUSTED_PROXY_IP=127.0.0.1/8,::1/128,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,fc00::/7,aaaa:bbbb:cccc:dddd::/64
ドキュメントを読む限り、アカウントIDで縛る処理がどこかにあるようなので、自分のIDを除外できれば出先でも起きなくなり、更に良さそうだが、やり方はよくわからなかった。
調べた限りconfig/initializers/rack_attack.rbの中でやっているようなので、暇なときに見てみたいとは思う。
Prometheusでメトリクスを取得する
Mastodonは標準でPrometheusに対応しているので、Exporterの有効化設定を見ながら設定していく。
まず.env.productionを開き、以下の二行を追記し、再起動する。これによってMastodonWebのExporterが有効化され、ローカルホストで動くようになるようだ。Streamingの方は設定せずとも、元から有効になっている模様。
MASTODON_PROMETHEUS_EXPORTER_ENABLED=true
MASTODON_PROMETHEUS_EXPORTER_LOCAL=true
再起動したらcurl http://localhost:{Streamingのポート番号}/metricsとcurl http://localhost:9394/metricsで動いていることを確認。
次にPrometheusのscrape_configsに以下を追記し、Prometheusを再起動する。
- job_name: 'mastodon-web'
static_configs:
- targets: ['localhost:9394']
- job_name: 'mastodon-streaming'
static_configs:
- targets: ['localhost:5001']
Prometheusの管理画面を開き、Target healthに出て動いていればOK。
【未解決】プロフィールを更新すると更新前の状態にぶり返したり、更新後の状態になったりして安定しない
大まかには、このような感じ。再現したりしなかったりでいい感じの動画が撮れなかった…。
キャッシュ周りで何かの問題が起きている可能性があるが原因はよくわかっていない。3分も待てば改善するので一旦気にしないことにした。
Dockerを使わなかった理由
Dockerがあれば一瞬で立ち上がって便利といえば便利なのだが、Ubuntuのネイティブ環境にPrometheusとGrafanaをIPv6スタックで導入した時に、DockerとIPv6の相性が余りにも悪すぎたのが最大の要因。
後はまぁ変なレイヤー隔ててない方が管理楽だし、リソースも抑えられるかなとか。
実際に導入して驚いた部分
簡単な監視ツールがある
Mastodon自体に監視の仕組みがあること。Sidekiqという名前自体はよく聞くので知っていたが、今まではなんとなくジョブ管理システムだと思っていた。これはTLで見かけていた管理人の人たちがトラブルが起きた時にジョブを殺したりしているのを見かけたためだ。
とはいえ、公式サイトの説明にはRuby's fastest job systemとあるので、あながち認識としては間違ってなかったようだ。ジョブ管理システムのついでにメトリクスとかも見れる機能があるのかもしれない。
Webベースの管理画面がある
環境変数周りの設定項目が多く、コンソールから設定を操作するイメージが強く、コンソールベースなのかと思っていたが実際にはWebベースのGUI管理コンソールもあり、中々便利だ。管理面の操作や、サーバー本体への影響がない簡単な設定はここで出来るだろう。
死ぬほど早い
アップロードが爆速だったり、画面遷移もかなり早い。家で繋ぐ分にはLAN環境だから早いのかと思っていたが、外から5Gで繋いでも早い。
今のところ大したアクセスや負荷もなく、リソースがしこたま余ってるお陰かもしれない。あとは単純に手前にCDNがないとか、VPSといった共有資源とかと比べた時のスペックの差だろうか?
スペックは結構ある方だと思う。具体的にはCPUはAMD Ryzen 5 5600G、メモリはDDR4 32GB、SSDもCrucial P1 CT500P1SSD8JPでNVMeといった感じで、そんなハチャメチャに高スペックというわけではないが、よくある?安い中古ノートPCを流用したサーバーと比べたら充実している。
今後やっていきたい部分
主に監視周りの強化をしていきたい。現在のロードマップは以下のような感じだ。
- Grafanaでnginxのダッシュボードを整備
- Lokiの導入
- GrafanaからMastodonのメトリクスを見れるように
- OpenWrt本体でメトリクスをとれるようにしたい
- 本体が死ぬとテレメトリ取れないので鯖側のPrometheusだと役不足
- 問題はストレージどうするか…
- 寿命の関係でeMMCには書けないからNVMe拡張するのか
- OpenWrtのメトリクスを鯖側でも取れるようにする(いるかこれ?
- 外部ステータスページの用意
- 簡単な外形監視の導入?(やるならさくらに乗せる)
まず、Grafanaをセットアップしたもののnginxのダッシュボードが空っぽなので作っていく必要がある。
次にLokiを入れてログ監視もできた方がいい。個別のログファイルを見るのは割とつらい。
Grafanaで統合管理できると便利なのでMastodonのメトリクスも取れると良いので、これもやっていきたい。
他にも今回のセットアップ中にOpenWrtがOutOfMemoryで落ちるケースがあり、原因不明のネットワーク切断が多発したため、こちらのメトリクス収集も必要だ。ネットワーク自体が死ぬと中身が見れないため、基本的に本体側で、収集して見れるようにしたい。今でも簡単なものは入れているが情報量が少なく、役不足だ。
ストレージ寿命の問題もありeMMCにログを吐くとストレージの寿命がマッハになる恐れがあるため、メモリ管理や追加でストレージを用意するなど、手法についても検討する必要がある。
OpenWrtのメトリクスを鯖側でも取れるようにするのはオプションプランだが、これは別にどうでもいい気はする。平時は統合的に見れて便利かもしれない。
あとは外部環境にステータスページを配置したいと考えている。実用性があるかどうかは謎だが、あるとそれっぽいから。ツールとしてはCachetを採用し、さくらのレンタルサーバー側で運用する予定だ。
簡単な外形監視の導入についても、Cachetで担える可能性がある。
監視面以外ではセルフホスティング方式のWAFであるAnubisに興味があり、これも入れていきたいと考えている。
更に中長期では、現在運用しているlycolia.info配下の全サイトを自宅サーバーへ移行し、メールサーバーやネームサーバーなど、現在あるWebサイトの一切を移行する野望も抱いている。
とにかくやることが沢山あるので、当面は暇になることはなさそうで、充実した生活を送れそうだ。
あとがき:OSグローバルのRubyを消してみた
本来手順になく、私の勘違いで追加して作業してしまっていたOSグローバルのRubyを消しても動くのか試してみた。
# 通常ユーザー作業
ls -la /usr/local/bin | grep ruby
rm /usr/local/bin/ruby-build
sudo rm -Rf /usr/local/bin/ruby-build
ls -la /usr/local/share
sudo rm -Rf /usr/local/share/ruby-build
sudo rm -Rf /usr/local/share/man/man1/ruby-build*
ruby -v
sudo apt remove ruby
sudo su - mastodon
# mastodonユーザー作業
mkdir -p "$(rbenv root)"/plugins/ruby-build
cp ruby-build-20250811/. "$(rbenv root)"/plugins/ruby-build
ls -la "$(rbenv root)"/plugins/ruby-build
cd live
RUBY_CONFIGURE_OPTS=--with-jemalloc rbenv install
ruby -v
exit
# 通常ユーザー作業
sudo service mastodon-web restart
sudo service mastodon-sidekiq restart
sudo service mastodon-streaming restart
一旦ここまでは上手くいったので、上の方にある手順よりこっちでやった方がいいかもしれない。Mastodonのビルドが通るかまでは試してないので、実際に動くかどうかまでは不明だが、まぁ次回考えればよいだろう。
Google Analyticsを廃止してMatomoを導入してみた結果、SQLでログを読めるようになったので、Apacheのログをインポートした結果から、来訪ユーザーのIPバージョン比率を集計してみた。
集計期間は2025年5月13日~2025年8月14日。
サイト別のIPバージョン比率
国内と国外からのアクセスのうちIPv4, IPv6の比率。
| サイト | 国内IPv4比 | 国内IPv6比 | 国外IPv4比 | 国外IPv6比 |
|---|---|---|---|---|
| lycolia.info | 68.93% | 31.07% | 95.61% | 4.39% |
| ブログ | 99.22% | 0.78% | 100.00% | 0.00% |
| Webツール置き場 | 100.00% | 0.00% | 100.00% | 0.00% |
| ECO-Wiki | 42.86% | 57.14% | 77.14% | 22.86% |
| 平均 | 77.75% | 22.25% | 93.19% | 6.81% |
サイト別のIPバージョン数
サイト別のIPバージョン比率の導出に使った元ネタ。各項目のユニークIPの個数。
| サイト | 国内IPv4数 | 国内IPv6数 | 国内累計 | 国外IPv4数 | 国外IPv6数 | 国外累計 | 総計 |
|---|---|---|---|---|---|---|---|
| lycolia.info | 5,420 | 2,443 | 7,863 | 3,746 | 172 | 3,918 | 23,562 |
| ブログ | 1,654 | 13 | 1,667 | 60,277 | 0 | 60,277 | 123,888 |
| Webツール置き場 | 73 | 0 | 73 | 545 | 0 | 545 | 1,236 |
| ECO-Wiki | 24 | 32 | 56 | 54 | 16 | 70 | 252 |
| 集計 | 7,171 | 2,488 | 9,659 | 64,622 | 188 | 64,810 | 148,938 |
ECO-Wikiの統計
ECO-WikiのIPv4とv6の数と比率。Googleからまともにインデックスされてないからか、かなり実際のユーザーの数に近い値が出たため、特別に取り上げている。
このサイトは1日のPVが200前後あり、集計期間が93日であることを考えると18.6kほどのPVがあったと思われるが、IPの本数としては非常に少なく、ほとんど固定ユーザーで回っていることが伺える。
| 国 | IPv4数(比率) | IPv6数(比率) | TOTAL |
|---|---|---|---|
| 日本 | 24 (42.86%) | 32 (57.14%) | 56 |
| タイ | 14 (70.00%) | 5 (25.00%) | 20 |
| 香港 | 13 (100.00%) | 0 (0.00%) | 13 |
| 中国 | 7 (63.64%) | 1 (9.09%) | 11 |
| インドネシア | 9 (90.00%) | 1 (10.00%) | 10 |
| 韓国 | 5 (62.50%) | 3 (37.50%) | 8 |
| 台湾 | 2 (28.57%) | 5 (71.43%) | 7 |
| シンガポール | 1 (100.00%) | 0 (0.00%) | 1 |
雑感
自宅サーバーに移行する場合、IPv6シングルスタックとなるわけだが、この割合だとかなり厳しい。特にメインコンテンツである、このブログのIPv6率が1%すらないのは極めて致命的だ。
むしろ特に価値のないlycolia.infoは31%ほどあり、比較的まともだ。ECO-Wikiに至っては57%を超えており、IPv6率が顕著に高い。
ただECO-Wikiはアクセスするユーザーが固定されていると考えており、大半が常連であるためこういう結果になっているのだとは思う。対してこのブログには恐らく常連はいないか、いても全体の1%いるかどうかだと思われる。
つまり常連が全員IPv6だとしても精々1%になるわけだ。
とはいえ、日本国内のIPv6対応状況はGoogleによると56%ほどあるとされており、来訪者が完全にランダムだとしても1%未満というのは異常すぎる。意味があるかは不明だが、念のためにaレコードより先にaaaaレコードを配置してみた。間違いなく意味はないと思う。
このままでは移行しても実質私以外見れないことが予想されるため、Matomoのカスタムディメンジョンを使ってIPv6に対応しているがIPv4でアクセスしているユーザーを調べることにした。
カスタムディメンジョンというのはアクセスログを取るときに追加のパラメーターを設定する機能だ。AAAAレコードしかないhttps://ipv6.lycolia.info/というドメインを切り、ここに疎通するかどうかを記録することにしている。1~3月くらい収集して、Matomoの生ログに以下のクエリを打てばおおよその比率は分かるだろう。
SELECT
SUM(CASE WHEN LENGTH(location_ip)=4 THEN 1 ELSE 0 END) AS ipv4_count,
ROUND(SUM(CASE WHEN LENGTH(location_ip)=4 THEN 1 ELSE 0 END)/COUNT(*)*100, 2) AS ipv4_ratio,
SUM(CASE WHEN LENGTH(location_ip)=16 THEN 1 ELSE 0 END) AS ipv6_count,
ROUND(SUM(CASE WHEN LENGTH(location_ip)=16 THEN 1 ELSE 0 END)/COUNT(*)*100, 2) AS ipv6_ratio,
SUM(CASE WHEN LENGTH(location_ip)=4 AND custom_dimension_1='v6ok' THEN 1 ELSE 0 END) AS ipv4_v6ok_count,
ROUND(SUM(CASE WHEN LENGTH(location_ip)=4 AND custom_dimension_1='v6ok' THEN 1 ELSE 0 END)/COUNT(*)*100, 2) AS ipv4_v6ok_ratio
FROM
matomo_log_visit
WHERE
custom_dimension_1 IS NOT NULL;
またApacheのログとGoogle Analyticsの内容に著しい乖離があったため、Apacheのログの信頼性も怪しい部分がある。というのはBOTの類が相当入っている可能性があり、BOTによるアクセスで比率が狂っている可能性も否定できない。とはいえ、本ブログではv6のIPが13個しか検出されてなかったので、BOT以前の問題だろう。
サーバーログとGoogle Analyticsとの乖離内容としてはシンガポールからのアクセスIP数が33kもあり、アメリカからも4kで、日本からのはたった1.5kしかなかったのだ。Google Analyticsのユーザーベースでは同じ期間で日本4.7k、シンガポール92、アメリカ29だったため、あからさまに異なる。これは恐らく多くのBOTはJSが動かない環境で動いており、GAのカウントから外れるのが大きいだろう。ApacheのログからはじけるBOTは精々UAに丁寧に書いてくれているか、ブラウザのバージョンが極端に古い典型的なものくらいで、まともに偽装しているやつは識別しようがないのでどうしようもない。
再三にはなるが本ブログではv6のIPが13個しか検出されてなかったので、それ以前の問題であることは明白なのだが…。
あとがき
MatomoのDBに入ったApacheのログが余りにもノイズなので消した。余りにも新規で取れるデータと内容に乖離がありすぎて、アクセス数が-300%みたいに出るし、通常運用では混入しない膨大なBOTアクセスログが見えるのが嫌だった。
消した時の行動としては、まずSSHで/path/to/matomoに移動し、./console core:delete-logs-data --dates=2025-04-01,2025-08-14のように削除したい期間を指定してコマンドを叩く、これでvisit系テーブルのデータが根こそぎ消える。次にmatomo_archive_blob_とmatomo_archive_numeric_で始まるテーブルをすべて消し、/console core:archive --url=<Matomoの設置URL>を叩いたら綺麗になった。
DROP TABLE matomo_archive_blob_2025_08;
...
DROP TABLE matomo_archive_numeric_2025_08;
...
visit系テーブルのデータを消しただけだと日別アクセス数は消えないので、これを消すために日別のレポートを記録しているテーブルを消し飛ばし、更にレポートのキャッシュをクリアする必要があった。
最近仕事で全然SQL打たないので、今回の一連の作業(統計出しを含む)はなんとも新鮮だった。
Google Analyticsには長らく不満があり、ほとんど活用できていなかったが、今回Matomoというアクセス解析を発見したので、これに乗り換えることにした。
何故Google Analyticsを使っていたか?
端的に言うと当時は他に選択肢を知らなかったから。
元々アクセス解析にはデジロックが運営するAccessAnalyzer.comというサービスを使っていたのだが、これがサービス終了したので仕方なくGoogle Analyticsに乗り換えたという経緯がある。
AccessAnalyzer.comは素晴らしく、ページ別のアクセスは勿論のこと、来訪者の検索ワードや検索エンジン、IPなどが分かりやすく見れる高機能なアクセス解析だった。今からしてみればプライバシーもなんもないが、アクセス解析というのは当時そういうものだった。更に動作も軽く、画面はパッと出てきた。
しかしGoogle Analyticsはそうではない。AccessAnalyzer.comで見れていた情報の大半は見れなくなり、精々ページ別のアクセス数が見れるだけだ。しかも画面が超絶重い。StableDiffusionが動くマシンでさえ重い。これは異常だ。
画面構成も判り辛く、とっつきづらい。ほぼ毎日のようにアクセス解析を眺めていた私は、Google Analyticsに移行してからは年に数回しか見なくなった。
それほどまでに魅力がなかった。プライバシーポリシーをサイトに書かないといけないのも癪だった。
セルフホスティングで動くアクセス解析には何があるか探してみた
まず私は昨今、さくらインターネットで動いているサイトを自宅サーバーへ移行することを考えている。その過程で、アクセス解析もセルフホスティングに移せないかを考えていた。
そこでセルフホスティングベースのアクセス解析を調べてみて、興味を引くものを幾つか見つけたので、簡単に機能比較をしてみた。GoAccessはアクセス解析というよりサーバーログ解析なので別物として扱った方がよい。
| - | GoAccess | Umami | Open Web Analytics | Matomo |
|---|---|---|---|---|
| CGI対応 | × | × | 〇 | 〇 |
| ランタイム | Go | Node.js | PHP | PHP |
| レスポンシブUI | 〇 | 〇 | × | 〇 |
| トラッキング | × | 〇 | 〇 | 〇 |
| IP収集 | 〇 | × | 〇 | 〇 |
| レポート | 〇 | 〇 | 〇 | 〇 |
| 初回リリース | 2010年 | 2020年 | 2016年 | 2007年 |
| GitHub Stars | 19.7k | 30.0k | 2.6k | 20.8k |
| 市場シェア | ? | ? | ? | 2.7% |
| 日本語対応 | × | 〇 | × | 〇 |
Matomoを選んだ理由
まずは何よりもCGIとして動作することだ。私はWebスクリプトの動作要件ではCGI動作可能かどうかを最重要視している。
理由としてはレンタルサーバーで動くからという部分が大きいが、自宅サーバーにおいてもリソースを食わないことや、メモリリークを起こしづらいことが魅力に感じている。個別にサーバーが起動しないため死活監視が不要なのもメリットだろう。nginxで動かす場合は、FastCGIの生死が見れたらよい。
また上の表でも最も〇評価が多く、私の求めている要件に大きく合致しているからだ。市場シェアと歴史もあり、継続的にメンテナンスされそうという期待も大きい。
アクセス解析に求めるもの
私は趣味でWebサイト運営をしているため、どういったユーザーが来ているのか、常連はどれほどいるのか?みたいな興味関心の部分に注視している。
日時別のアクセス統計
ある日時にどのページにどの程度アクセスがあったかをグラフなどで表示できる機能は基本的に欲しい。逆にこれがないのならサーバーログをパースして見た方がマシだ。
リファラデータ
セキュリティが強化された現在においては、具体的にどこからアクセスがあったかというのは追いづらいが、それでもドメインくらいは見れるため、Googleから来たのか、Xから来たのか、社内SNSから来たのかなど、ある程度特定できるのは個人的に興味がある。
ユーザートラッキング
ユーザーにトラッキングCookieを付与することで、いったいどれほどのユーザーが再訪しているのか、何を見ているのか?というのを見るのに使っている。古典的なサイト運営者としては常連がいることが分かると、やはり嬉しい。
UserAgentや国、地域情報などの環境情報
どんなブラウザや、国、地域から来ているのかわかるのは興味深い。例えば私は神戸に住んでいるので関西圏からのアクセスが多ければ少し嬉しくなったりするし、シンガポールからアクセスがあれば驚くこともある。
他にも使用しているブラウザやOSなどもわかるとなんとなく楽しい。
IPアドレス
トラッキングIDがなかった時代はこれをトラッキングコードの代わりにしていたが、実は今では重要性はほとんどない。
個人的な直近の需要としてはIPv4とIPv6の比率が分かればいいので、IPアドレスそのものはなくてもかまわない。但し現状ではDBの生ログをベースに集計せざるを得ない状況なので、有用な機能だ。
Matomoを選んでよかった理由
既存のアクセスログの解析ができた
さくらのレンタルサーバーにあるApacheのログを食わせてDBに登録できるため、SQLベースの解析ができるのは有益だった。Google Analyticsでは見れない角度で見ることもできた。
但しサーバーログはノイズも多く、トラッキングもできないため、自分のアクセスすら特定が困難で、あまり使えなかった。この結果については別記事にする予定だ。
画面読み込みが圧倒的に軽い
私のマシンはCore Ultra 7 265FにRTX 5070 Ti、メモリを64GB積み、fast.comでの回線速度が360Mbps、遅延が5msの性能があるが、この環境をもってしてもGoogle Analyticsの画面読み込みや、画面遷移は非常に遅く、使うのが億劫だった。
しかしMatomoは画面遷移が極めて速く、何らストレスがない。
リアルタイム解析が見やすい
ここまで直感的にわかるのは便利だ。いつだれがどこに、どんな環境でどこから来ているかが一目瞭然である。
さくらのレンタルサーバーで動く
さくらのレンタルサーバーでも動くのはありがたい。自宅サーバーへの移行はまだできていないし、色々あって前途が怪しい部分もあるからだ。
ログDBが手元にあるので調査に便利
ログDBが手元にあるため、画面上では見れないデータを見る場合にも便利だ。
例えばApacheのログを食わせた後に自分がどのサイトをどの端末から見ていて、IPのバージョンを追跡したい場合、次のようにして調べることが出来る。このようにMatomo画面では見れないデータも柔軟に見れるのは非常に便利だ。
SELECT
*
FROM
(
SELECT
visit_last_action_time,
idsite,
CASE
WHEN LENGTH(location_ip) <= 4 THEN INET6_NTOA(location_ip)
ELSE ''
END AS ipv4,
CASE
WHEN LENGTH(location_ip) > 4 THEN INET6_NTOA(location_ip)
ELSE ''
END AS ipv6,
config_browser_name,
config_os
FROM matomo_log_visit
) TBL
WHERE
ipv4 = '自分のIPv4アドレス'
OR ipv6 LIKE '自分のIPv6アドレスの先頭4フィールド%'
ORDER BY
visit_last_action_time DESC;
あとがき:地味にある日本語由来のソフトウェア
UmamiとかMatomoとか、日本語ベースのアクセス解析が複数あるのはちょっと面白いなと思った。見た感じ、どちらも日本人の開発ではなさそうだ。
由来としてはUmamiはご飯のアイコンからして旨味なのだろうが、明確な由来は見つけられなかった。Matomoは日本語におけるhonestyの意味から取られたそうだ。
よく考えてみるとWebソフトウェアには日本語由来のものが結構あるかもしれない。例えば他にもPythonのJinjaやCSSフレームワークのBulmaが日本語に由来している。
blog.lycolia.info
去年の終わりごろから落ち込んでいたGoogle検索流入がここ最近、急激に増えている。理由は不明。きっといつものGoogleの気分だろう。
CTRや表示順位は下がっているので、AIサジェストのソースリンクからの流入なのかもしれない。
流入先ページ上位10位
端的に言うとトラブルシューティング系の記事が多い。
| 順位 | ページ | 投稿日 |
|---|---|---|
| 1 | バッチファイルで文字列置換する | 2023/03/09 |
| 2 | 久々にCOM3D2を起動して発生したトラブルの解消メモ | 2023/05/21 |
| 3 | 業務用と個人用でGitHubの無料アカウントを分ける | 2022/07/03 |
| 4 | Oculus Touchのスティックでオダメの移動操作をする設定 | 2019/02/23 |
| 5 | Visual Studio 2022 Communityで手軽にC#.NETのコードカバレッジを見る方法 | 2023/03/23 |
| 6 | Windows10のリモートデスクトップで音声遅延を抑える方法 | 2021/06/09 |
| 7 | Windows 11で接続中のNASからログアウトし、別ユーザーで入りなおす | 2024/02/01 |
| 8 | Windows 11のChromeで正確な位置情報を取得できるようにする | 2023/02/09 |
| 9 | エコバッグを使うのをやめた話 | 2024/06/19 |
| 10 | さくらのレンタルサーバーにNode.jsをインストールする | 2024/01/28 |
eco.lycolia.info
こちらは2024/04/08に開設したサイトで、当初はGoogleにインデックスされていたものの、途中からインデックスが消え、最近復活したサイトだ。
具体的には開設当初はGoogleからインデックスされていたものの、一ヵ月ほどでGoogle Search Console上からインデックス数が0になり、インデックス数は0なのに、5ページくらいは検索に引っかかる状態だったサイトだ。
サマリーを見ると1,375%も流入が増えているらしく、中々すさまじい。
実際、爆発的にGoogle検索からの流入が増えている。
このサイトは、かつて存在したエミル・クロニクル・オンラインというMMORPGのWikiで、前任の管理者が運営を停止するというので引き継いだものだ。中身はPukiwiki。
前任の管理者はサイト停止日までGoogleにインデックスリクエストを出さないでほしいと要望していたが、私は無視してインデックスリクエストしていた。但しコピーサイトとして判定され、恐らくペナルティでインデックスが止まっていた。
前述の理由もあり、前のサイトから私の新サイトへは有効なSEO効果のあるリダイレクト対応は一切行われず、前サイトは2024/11/04に停止した。
Googleはサイトの廃止判定に半年かかるという持論があるため、恐らく半年後になればインデックスされるであろうと私は睨み、日々サイトのメンテナンスに励んだ。
例えば意味がなくともインデックスリクエストを送る、Wikiのパフォーマンスを上げるために独自の改造を極力排除、Pukiwikiは複数のURLがあるためcanonical urlの指定を入れる、旧サイトへリンクしているサイトへのリンク書き換え要請などを行い続け、結果としておよそ半年後の2025/06/06に、無事トップページがインデックスされた。
ページの多くはインデックスされないままでいるが、とりあえずトップページだけでも引っかかれば需要はつかめるはずなので問題ないだろう。
ここ最近ブログをちょいちょいアップデートしたのでそのログを残す。
画像サムネイルの縮小
2025年6月以降の記事の画像サムネイルを320pxから240pxへ縮小している。
これは本サイトが画像より文章を優先しているためだ。今までは画像がデカすぎるなと思っていたが、これで筆者は付帯文章が読みやすくなった。
それ以前の記事に関してはチェックする余裕がないので、そのままにしている。
非公開コメント機能の廃止
トップページにも書いているが、7月7日にブログの非公開でコメントする機能を無効化し、機微な情報を含まないコメントをすべて公開ている。
個人的にブログというのはコメントが付いてなんぼだと思っており、あまり非公開でコメントしてほしくない部分があったからだ。コメントは記事の信憑性や鮮度に繋がり、サイト全体の価値向上に繋がるため、基本的に公開したいスタンスだ。
このため個人情報などの機微な情報を含まないものは一律公開設定に変更している。
余談だが、adiaryのメタデータの作りが微妙に難解で苦戦した。記事の公開にかかわるフラグが二つあり、その二つを調整しないと公開にならなかった。この辺りの複雑性は歴史あるシステムなので仕方ないだろう。adiaryは中々保守に手がかかるので、そのうちadiaryの機能をベースにした、全く別のCMSに置き換えたいところではある。
幸い全体的な機能や設計の出来は素晴らしく、余計なものを削り落とし、保守性を挙げて作り直せれば、これは大変すばらしいプラットフォームになると考えている。とはいえ、あまりに出来が良すぎるので同等のものを作るだけでも大変そうだが…w

















