SSD Nodes Learn Hosting plans →
ガイド Matt Connor著者 Matt Connor ・更新日 2026-08-26

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 が表示される場合、ほぼ常に原因はデータベースです。.envDB_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 キーを作成する

API はキーなしでは呼び出せません。コンテナ内の CLI からキーを生成します。

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

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

すべての REST 呼び出しでは、X-Api-Key ヘッダーにキーを設定します。

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

shortUrls キーを含む JSON オブジェクトが返れば、キーは機能しています。INVALID_API_KEY を含む 401 が返る場合、キーが誤っているか、無効化されているか、有効期限が切れています。

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

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 クライアントと QR コード

Web クライアントは 127.0.0.1:8081 にあり、専用のプロキシエントリが必要です。公開したくない場合は SSH トンネルを使用します。初回の読み込み時に、サーバー URL と API key の入力を求められます。https://s.example.com と生成した key を入力してください。クライアントは両方をブラウザーのストレージに保存し、API を直接呼び出します。そのため、データが第三者を経由することはありません。インターフェースを API から分離する構成は注目に値します。これは、メディアサーバー自体を変更せずに、Halcyon で Jellyfin のライブラリを 1990 年代のレンタル店のように見せることを可能にする構成と同じです。

QR コードには設定が一切必要ありません。任意の短い URL に /qr-code を追加すると、API が画像を返します。

https://s.example.com/docs/qr-code?size=500&format=svg&margin=20

size はピクセル単位の幅で、50 から 1000 まで指定できます。デフォルトは 300 です。formatpng または svg です。margin はコードの周囲に設ける余白のピクセル数です。完成した画像のサイズは、コードのサイズに余白の 2 倍を加えたものになります。errorCorrection=Q を追加すると、小さく印刷した場合や一部が覆われた場合でも読み取れるコードになります。

稼働を継続する

短縮サービスは、ひそかに停止することがあります。リンクのリダイレクトが止まっても、クリックした人はリンクが無効になっただけだと思うため、誰も知らせてくれません。ホームページではなく実際の短縮 URL を対象に稼働時間チェックを設定し、リダイレクト以外の応答をすべてアラート対象にします。自己ホスト型の Uptime Kuma インスタンスがこの用途に適しており、特定のステータスコードも監視できます。

コンテナではなく、データベースをバックアップします。1 つのコマンドでダンプを取得できます。

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

このファイルと compose ファイルがあれば、新しいサーバー上でサービス全体を再構築できます。サーバー上の各アプリについて、この組み合わせを個別に用意する必要があります。写真ライブラリは例外的に扱いが難しくなります。PhotoPrism と Immichは、データベースの行だけでなく、元の画像もディスク上に保存するためです。そのため、ダンプだけでは何も復元できません。アップグレードは sudo docker compose pull の後に sudo docker compose up -d を実行し、Shlink は起動時に新しいマイグレーションを実行します。pull の前にダンプを取得してください。マイグレーションはロールバックできないためです。

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