SSD Nodes Learn Hosting plans →
ガイド Matt Connor著者 Matt Connor

ShlinkとDocker Composeで自前のURL短縮サービスを構築

VPS上でShlink 5.1とDocker Composeを使い、短縮ドメインのDNS、Postgres、API key、web client、QRコード、クリック統計を設定する手順です。

構築するもの

セルフホスト型の URL 短縮サービスは、長いリンクを自分で管理する短い URL に変換し、そのクリック数をすべて記録する小規模なサーバーです。選ぶべきなのは Shlink です。オープンソースで、Docker イメージとしてリリースされており、1 つのコンテナとデータベースで必要な処理を実行できます。このガイドでは、実際の短縮ドメインを使用し、HTTPS、API key、QR コード、クリック統計に対応した構成を VPS 上に構築します。

商用の短縮サービスのように使える構成は、2 つの要素で成り立ちます。API server はリダイレクトに応答し、データを保持します。web client は独立した静的アプリケーションで、ブラウザーからその API と通信します。両方を実行することも、API だけを実行して command line から操作することもできます。

ここで示すバージョン番号は、2026 年 7 月時点での現行バージョンです。Shlink は 5.1、shlink-web-client は 4.8 です。

短いドメインを最初にサーバーへ向けます

ドメインはサービスの一部です。s.example.com/abc123 は利用者に表示されるリンクなので、短い名前を選び、何かをインストールする前に決めてください。Shlink はすべての短縮 URL にドメインを保存します。後から変更すると、すでに配布したすべてのリンクが機能しなくなります。

短いドメインに対する DNS A レコードを1つ作成し、VPS のパブリック IPv4 アドレスを指定します。サーバーに IPv6 がある場合は、AAAA レコードも追加してください。続行する前に、名前が解決されることを確認します。

dig +short s.example.com A

出力はサーバーのアドレスでなければなりません。空の場合、レコードがまだ伝播していません。そのまま進むと、後続の手順は分かりにくい形で失敗します。名前解決できない名前に対して TLS (transport layer security) 証明書を発行できないためです。

Compose ファイル

Shlink にはデータベースが必要です。テストでは SQLite でも動作しますが、保持する予定のデータには Postgres が適しています。アクセス行は蓄積され、Postgres のほうがインデックスと同時書き込みを適切に処理できるためです。/opt/shlink/compose.yaml に次の内容を記述します。

services:
  shlink:
    image: shlinkio/shlink:stable
    restart: unless-stopped
    ports:
      - "127.0.0.1:8080:8080"
    environment:
      DEFAULT_DOMAIN: s.example.com
      IS_HTTPS_ENABLED: "true"
      DB_DRIVER: postgres
      DB_HOST: database
      DB_NAME: shlink
      DB_USER: shlink
      DB_PASSWORD: ${DB_PASSWORD}
    depends_on:
      - database

  database:
    image: postgres:17-alpine
    restart: unless-stopped
    environment:
      POSTGRES_DB: shlink
      POSTGRES_USER: shlink
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - shlink_db:/var/lib/postgresql/data

  web-client:
    image: shlinkio/shlink-web-client:stable
    restart: unless-stopped
    ports:
      - "127.0.0.1:8081:8080"

volumes:
  shlink_db:

公開する2つのポートはどちらも 127.0.0.1 にバインドされます。そのため、次のセクションでリバースプロキシを設定するまで、インターネットからは到達できません。Docker はホストのファイアウォールより先に独自の転送ルールを適用します。そのため、ファイアウォールが閉じているように見えるホストでも、単純な 8080:8080 行だけでアプリが公開されます。ループバックアドレスにバインドすれば、これを防げます。同じ方法は、この方式で実行するすべてのアプリに適用できます。詳しくは VPS で Docker Compose を使用するガイド を参照してください。

データベースのパスワードは、Compose ファイルと同じディレクトリにある .env ファイルから読み込みます。そのため、YAML にパスワードが残りません。

sudo mkdir -p /opt/shlink
printf 'DB_PASSWORD=%s\n' "$(openssl rand -base64 24)" | sudo tee /opt/shlink/.env
sudo chmod 600 /opt/shlink/.env

起動して、API が立ち上がる様子を監視します。

cd /opt/shlink
sudo docker compose up -d
sudo docker compose logs -f shlink

初回起動ではデータベースのマイグレーションが実行されるため、2回目以降より時間がかかります。起動が完了したら、サービスがローカルで応答することを確認します。

curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/rest/health

200 は、API が稼働し、データベース接続も機能していることを示します。ここで 500 が表示される場合、ほぼ常に原因はデータベースです。.env の DB_PASSWORD が、Postgres の作成時に指定した値と一致していないためです。Postgres イメージは、空のデータディレクトリを初期化するときにだけ POSTGRES_PASSWORD を読み込みます。後からパスワードを編集しても、ボリュームを削除して再度起動するまで反映されません。

前段で HTTPS を終端する

Shlink はポート 8080 で平文の HTTP を提供します。TLS はリバースプロキシで終端し、元のホスト名を転送する設定が重要です。Shlink は Host ヘッダーを読み取り、短縮コードが属するドメインを判断します。そのため、プロキシがこのヘッダーを書き換えると、存在するリンクでも 404 応答になり、アクセス統計が誤ったドメインに紐づきます。

server {
    server_name s.example.com;
    listen 80;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

続いて証明書を発行します。更新タイマーを含む手順全体は、Ubuntu 24.04 で nginx に Certbot を設定するガイドに記載されています。

sudo certbot --nginx -d s.example.com

compose ファイルの IS_HTTPS_ENABLED: "true" によって、Shlink は返す短縮 URL に https:// を出力します。これは TLS を単独で有効にする設定ではありません。HTTPS プロキシの背後に false のまま配置すると、API が返すすべてのリンクは http:// リンクになり、その後リダイレクトされます。これにより余分なラウンドトリップが発生し、Web クライアント上でも不自然に見えます。

API key を作成する

API は key がなければ通信できません。コンテナ内の CLI から key を生成します。

sudo docker compose exec shlink shlink api-key:generate --name "web client"

このコマンドは key を 1 回だけ表示します。hashed 形式で保存され、再表示できないため、今すぐコピーしてください。shlink api-key:list では各 key の名前と有効・無効だけを確認でき、key 自体は表示されません。shlink api-key:disable と名前を指定すると、key を無効化できます。

すべての REST 呼び出しでは、X-Api-Key header に key を含めます。

curl -H "X-Api-Key: YOUR_KEY" https://s.example.com/rest/v3/short-urls

shortUrls key を含む JSON object が返れば、key は機能しています。type が https://shlink.io/api/error/invalid-api-key で、title が Invalid API key の 401 は、key が誤っているか、無効になっているか、有効期限を過ぎていることを示します。

コマンドラインから短縮リンクを作成する

CLI はリンクを作成する最も速い方法であり、スクリプトにも適しています。

sudo docker compose exec shlink shlink short-url:create https://example.com/a/very/long/path
sudo docker compose exec shlink shlink short-url:create https://example.com/docs --custom-slug docs --tag reference

--custom-slugを使用すると、生成されたコードではなく、読みやすいリンクを作成できます。スラッグはドメインごとに一意です。そのため、使用済みのスラッグを指定して再度作成すると、最初のリンクを上書きせずに失敗します。--tagは繰り返し指定できます。タグを使うと、後でまとめて統計を確認したいリンクをグループ化できます。

存在するリンクを一覧表示し、1 つのリンクのトラフィックを確認します。

sudo docker compose exec shlink shlink short-url:list
sudo docker compose exec shlink shlink short-url:visits docs

short-url:visitsを使用すると、クリックごとに、日付、リファラー、ユーザーエージェントを 1 行ずつ表示できます。国と都市の列は、GEOLITE_LICENSE_KEY環境変数を設定しない限り空のままです。これは、Shlink が GeoLite2 データベースをダウンロードするために使用する無料の MaxMind key です。これを設定しなくても訪問は記録されますが、位置情報は取得されません。

Web client と QR code

Web client は現在 127.0.0.1:8081 で利用でき、専用の proxy 設定が必要です。公開したくない場合は SSH tunnel を使用します。初回の読み込み時に server URL と API key の入力を求められます。https://s.example.com と、生成した key を入力してください。client は両方を browser storage に保存し、API を直接呼び出します。そのため、データが第三者を経由することはありません。interface を API から分離する構成は注目に値します。これは、media server 自体を変更せずに Halcyon で Jellyfin の library を 1990 年代のレンタル店風に見せることも可能にする、同じ構成パターンです。

QR code は API ではなく web client で生成します。Shlink 5 では、short URL の末尾に /qr-code を追加する従来の方法が削除されたため、その URL は画像を返さなくなりました。代わりに web client が browser 内で code を描画するため、Shlink への request は発生しません。short URL list で任意の link の QR code option を開き、size、format、margin を設定して結果を download します。小さく印刷した場合や一部が隠れた場合でも読み取れるようにするには、error correction level を上げてください。同じ dialog で色と logo も設定できます。

稼働を維持する

shortener は静かに障害を起こします。link の redirect が停止しても、クリックした人は link が無効になったと思うため、誰も知らせてくれません。home page ではなく実際の short URL を uptime check の対象にし、redirect 以外の応答を alert の対象にしてください。自己ホスト型の Uptime Kuma instance が適しており、特定の status code を監視することもできます。

バックアップする対象は container ではなく database です。1 つの command で dump を作成できます。

sudo docker compose exec -T database pg_dump -U shlink shlink | gzip > shlink-$(date +%F).sql.gz

この file と compose file があれば、新しい server 上で service 全体を再構築できます。server 上の各 app には、この組み合わせをそれぞれ用意する必要があります。photo library は扱いにくい例です。PhotoPrism と Immich は database の row だけでなく original file も disk 上に保持するため、dump だけでは account と設定は復元できても、写真は 1 枚も戻りません。upgrade は sudo docker compose pull の後に sudo docker compose up -d を実行します。Shlink は起動時に新しい migration を実行します。migration は rollback できないため、pull の前に dump を作成してください。

FAQ

短縮リンクにリバースプロキシを追加すると 404 が返るのはなぜですか?

Shlink は、Host ヘッダーのドメインに対して短縮コードを照合します。プロキシが独自の名前や内部アドレスを送信すると、Shlink はリンクが存在しないドメインでそのコードを探すため、404 を返します。nginx の location ブロックに proxy_set_header Host $host; を設定し、プロキシを reload してください。コンテナを再起動しなくても、すぐにリンクが機能します。

Postgres は必要ですか?SQLite で十分ですか?

Shlink を試すだけなら SQLite で問題なく、2 つ目のコンテナも必要ありません。重要なリンクを公開する前に Postgres へ移行してください。クリックごとに訪問レコードが増え、SQLite は書き込みを直列化するためです。後から切り替える場合はリンクを export して再 import する必要があるため、最初から Postgres を選ぶと移行作業を省けます。

コピーし忘れた API key を復元できますか?

できません。Shlink は key の hash を保存するため、api-key:list で名前と状態は表示できますが、値は表示できません。shlink api-key:generate で replacement を生成し、web client に貼り付けてください。その後、shlink api-key:disable で古い key を無効化すると、使用できなくなります。

訪問統計の国別カラムが空なのはなぜですか?

Geolocation には GeoLite2 database が必要です。Shlink は GEOLITE_LICENSE_KEY を指定した場合にのみ database を download します。key は MaxMind から無料で取得できます。環境変数のセクションに追加してコンテナを recreate すると、新しい訪問が location 付きで記録されます。それ以前に記録された訪問は、shlink visit:locate を実行するまで空のままです。

ドメインを維持し、データを移行してください。pg_dump で database を dump し、dump と compose file を新しいサーバーへ copy します。stack を start し、実際の traffic が到着する前に空の database へ dump を restore してください。DNS record は最後に変更します。短縮コードと訪問履歴は database にすべて保存されているため、そのまま維持されます。

#shlink#url-shortener#self-hosting#docker#postgres