SSD Nodes Learn Hosting plans →
Panduan Matt ConnorOleh Matt Connor · Diperbarui 2026-08-29

Cara Membuat URL Shortener Sendiri dengan Shlink

Buat URL shortener di VPS dengan Shlink 5.1 dan Docker Compose, termasuk DNS domain singkat, Postgres, API key, web client, QR code, serta statistik klik.

Yang akan Anda bangun

URL shortener yang di-host sendiri adalah server kecil yang mengubah tautan panjang menjadi tautan pendek milik Anda dan menghitung setiap klik pada tautan tersebut. Shlink adalah pilihan yang tepat: perangkat lunak ini bersumber terbuka, tersedia sebagai image Docker, dan menjalankan seluruh fungsi tersebut dalam satu container beserta database. Panduan ini memasangnya pada VPS di balik domain pendek khusus, dengan HTTPS, API key, QR code, dan statistik klik.

Dua komponen membuatnya terasa seperti layanan pemendek URL komersial. API server menangani redirect dan menyimpan data. Web client adalah aplikasi statis terpisah yang berkomunikasi dengan API tersebut dari browser Anda. Anda dapat menjalankan keduanya atau hanya menjalankan API dan menggunakannya melalui command line.

Nomor versi dalam panduan ini adalah versi yang berlaku pada Juli 2026: Shlink 5.1 dan shlink-web-client 4.8.

Terlebih dahulu arahkan domain singkat ke server

Domain adalah bagian utama layanan. s.example.com/abc123 adalah tautan yang dilihat orang, jadi pilih nama yang singkat sebelum menginstal apa pun. Shlink menyimpan domain tersebut pada setiap URL singkat. Jika domain diubah kemudian, semua tautan yang sudah dibagikan akan berhenti berfungsi.

Buat satu record DNS A untuk domain singkat tersebut dan arahkan ke alamat IPv4 publik VPS Anda. Tambahkan record AAAA jika server memiliki IPv6. Kemudian, pastikan domain tersebut dapat di-resolve sebelum melanjutkan.

dig +short s.example.com A

Output harus berupa alamat server Anda. Jika output kosong, record tersebut belum terpropagasi. Semua langkah berikutnya akan gagal dengan cara yang membingungkan karena sertifikat TLS (transport layer security) tidak dapat diterbitkan untuk nama yang tidak dapat di-resolve.

Berkas compose

Shlink memerlukan database. SQLite cukup untuk pengujian, tetapi Postgres adalah pilihan yang tepat untuk apa pun yang akan Anda simpan karena baris kunjungan terus bertambah dan Postgres menangani indeks serta penulisan bersamaan dengan lebih baik. Simpan konfigurasi ini di /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:

Kedua port yang dipublikasikan terikat ke 127.0.0.1, sehingga tidak ada yang dapat diakses dari Internet sampai reverse proxy pada bagian berikutnya siap digunakan. Docker menulis aturan penerusan sendiri sebelum firewall host, sehingga baris 8080:8080 biasa akan mengekspos aplikasi meskipun firewall pada server terlihat tertutup. Mengikat port ke alamat loopback mencegah hal itu. Pola yang sama berlaku untuk aplikasi apa pun yang Anda jalankan dengan cara ini, dan penjelasan yang lebih rinci tersedia dalam panduan Docker Compose pada VPS.

Password database berasal dari file .env di sebelah berkas compose, sehingga password tersebut tidak pernah masuk ke 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

Jalankan dan pantau hingga API aktif.

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

Pada proses start pertama, migrasi database dijalankan sehingga waktunya lebih lama daripada proses berikutnya. Setelah proses selesai, periksa apakah service merespons secara lokal.

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

Respons 200 berarti API aktif dan koneksi database berfungsi. Respons 500 di sini hampir selalu menunjukkan masalah pada database: DB_PASSWORD dalam .env tidak sama dengan yang digunakan saat Postgres dibuat, karena image Postgres hanya membaca POSTGRES_PASSWORD ketika menginisialisasi direktori data yang kosong. Mengedit password setelah itu tidak berpengaruh sampai Anda menghapus volume lalu menjalankan kembali service.

Terminasi HTTPS di depannya

Shlink melayani HTTP biasa pada port 8080. TLS harus ditangani oleh reverse proxy, dan satu pengaturan yang penting adalah meneruskan nama host asli. Shlink menentukan domain tujuan suatu kode pendek dengan membaca header Host. Jika proxy menulis ulang header tersebut, tautan yang sebenarnya ada akan menghasilkan respons 404, dan statistik kunjungan akan dikaitkan dengan domain yang salah.

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;
    }
}

Selanjutnya, terbitkan sertifikat. Panduan lengkapnya, termasuk timer untuk pembaruan, tersedia di panduan Certbot untuk nginx di Ubuntu 24.04.

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

IS_HTTPS_ENABLED: "true" dalam file compose membuat Shlink menampilkan https:// pada URL pendek yang dikembalikannya. Pengaturan tersebut tidak mengaktifkan TLS. Biarkan false berada di belakang proxy HTTPS. Dengan demikian, setiap tautan yang dikembalikan API merupakan tautan http:// yang kemudian melakukan redirect, sehingga menambah satu perjalanan pulang-pergi dan tampak keliru di klien web.

Buat kunci API

Tidak ada aplikasi yang dapat berkomunikasi dengan API tanpa kunci. Buat kunci melalui CLI di dalam container.

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

Perintah tersebut menampilkan kunci satu kali. Salin sekarang karena kunci disimpan dalam bentuk hash dan tidak dapat ditampilkan lagi. shlink api-key:list menampilkan nama setiap kunci dan status aktifnya, bukan kuncinya. Cabut kunci dengan shlink api-key:disable dan namanya.

Setiap panggilan REST membawa kunci dalam header X-Api-Key.

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

Objek JSON dengan kunci shortUrls menunjukkan bahwa kunci berfungsi. 401 yang berisi INVALID_API_KEY menunjukkan bahwa kunci salah, dinonaktifkan, atau sudah melewati tanggal kedaluwarsa.

CLI adalah cara tercepat untuk membuat link dan cocok digunakan dalam script.

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 memberi Anda link yang mudah dibaca, bukan kode yang dibuat secara otomatis. Slug bersifat unik untuk setiap domain, sehingga percobaan kedua menggunakan slug yang sudah dipakai akan gagal dan tidak menimpa link pertama secara diam-diam. --tag dapat diulang, dan tag digunakan untuk mengelompokkan link yang nantinya ingin Anda lihat statistik gabungannya.

Tampilkan link yang ada, lalu periksa trafik salah satu link.

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

short-url:visits menampilkan satu baris untuk setiap klik, yang berisi tanggal, referrer, dan user agent. Kolom negara dan kota tetap kosong kecuali Anda menetapkan variabel lingkungan GEOLITE_LICENSE_KEY, yaitu key MaxMind gratis yang digunakan Shlink untuk mengunduh database GeoLite2. Tanpa variabel tersebut, kunjungan tetap dicatat, tetapi lokasinya tidak ditentukan.

Klien web dan kode QR

Klien web kini berada di 127.0.0.1:8081 dan memerlukan entri proxy sendiri, atau tunnel SSH jika Anda tidak ingin memublikasikannya. Saat pertama kali dimuat, klien meminta URL server dan API key. Masukkan https://s.example.com dan key yang Anda buat. Klien menyimpan keduanya di browser storage dan memanggil API Anda secara langsung, sehingga tidak ada data yang melewati pihak lain. Memisahkan antarmuka dari API merupakan pola yang perlu diperhatikan, karena pola yang sama memungkinkan Halcyon mengubah library Jellyfin menjadi toko persewaan bergaya 1990-an tanpa mengubah media server di belakangnya.

Kode QR tidak memerlukan konfigurasi apa pun. Tambahkan /qr-code ke URL pendek apa pun, lalu API mengembalikan gambarnya.

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

size adalah lebar dalam piksel dan menerima nilai 50 hingga 1000, dengan nilai default 300. format adalah png atau svg. margin adalah ruang kosong di sekeliling kode dalam piksel, dan ukuran gambar akhir adalah ukuran tersebut ditambah dua kali margin. Tambahkan errorCorrection=Q untuk kode yang tetap dapat dipindai saat dicetak dalam ukuran kecil atau tertutup sebagian.

Pertahankan agar tetap berjalan

Layanan pemendek URL dapat gagal tanpa memberikan tanda. Tautan berhenti melakukan redirect dan tidak ada yang memberi tahu Anda, karena orang yang mengekliknya mengira tautan tersebut sudah mati. Arahkan pemeriksaan uptime ke URL pendek yang benar-benar digunakan, bukan ke halaman utama, lalu buat alert untuk apa pun yang bukan redirect. Instance Uptime Kuma yang di-host sendiri dapat melakukannya dengan baik dan dapat memantau kode status tertentu.

Cadangkan database, bukan container. Satu perintah dapat membuat dump.

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

File tersebut, bersama file compose Anda, dapat membangun ulang seluruh service pada server baru. Setiap aplikasi di server memerlukan versi tersendiri dari pasangan file tersebut. Pustaka foto adalah kasus yang lebih rumit karena PhotoPrism dan Immich menyimpan file asli di disk serta baris data di database. Karena itu, dump saja tidak dapat memulihkan apa pun. Upgrade dilakukan dengan sudo docker compose pull lalu sudo docker compose up -d, dan Shlink menjalankan migration baru saat start. Buat dump sebelum menjalankan pull karena migration tidak dapat di-rollback.

FAQ

Shlink mencocokkan kode singkat dengan domain dalam header Host. Proxy yang mengirimkan namanya sendiri atau alamat internal membuat Shlink mencari kode tersebut pada domain yang tidak memiliki link, sehingga mengembalikan 404. Tetapkan proxy_set_header Host $host; dalam blok lokasi nginx, lalu muat ulang proxy. Link akan langsung berfungsi tanpa me-restart container.

Apakah saya memerlukan Postgres, atau SQLite sudah cukup?

SQLite cukup untuk mencoba Shlink dan tidak memerlukan container kedua. Beralihlah ke Postgres sebelum Anda memublikasikan link penting, karena baris kunjungan bertambah pada setiap klik dan SQLite membuat operasi tulis berjalan secara serial. Jika beralih nanti, Anda harus mengekspor dan mengimpor kembali link. Memilih Postgres sejak awal menghindarkan Anda dari migrasi tersebut.

Apakah saya dapat memulihkan API key yang lupa saya salin?

Tidak. Shlink menyimpan hash key tersebut, sehingga api-key:list menampilkan nama dan status, tetapi tidak pernah menampilkan nilainya. Buat pengganti dengan shlink api-key:generate, tempelkan ke web client, lalu nonaktifkan key lama dengan shlink api-key:disable agar tidak dapat digunakan lagi.

Mengapa kolom negara kosong dalam statistik kunjungan saya?

Geolokasi memerlukan database GeoLite2, yang hanya diunduh Shlink jika Anda memberikan GEOLITE_LICENSE_KEY. Key tersebut tersedia gratis dari MaxMind. Tambahkan ke bagian environment, buat ulang container, dan kunjungan baru akan mendapatkan lokasi. Kunjungan yang tercatat sebelumnya tetap kosong sampai Anda menjalankan shlink visit:locate.

Pertahankan domain dan pindahkan datanya. Dump database dengan pg_dump, salin dump dan file compose ke server baru, jalankan stack, lalu pulihkan dump ke database kosong sebelum trafik sebenarnya masuk. Ubah record DNS sebagai langkah terakhir. Kode singkat dan riwayat kunjungannya tetap ada karena semuanya tersimpan dalam database.

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