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 Anda bangun
URL shortener yang di-host sendiri adalah server kecil yang mengubah tautan panjang menjadi tautan pendek milik Anda, serta menghitung setiap klik pada tautan tersebut. Shlink adalah pilihan yang tepat: software ini open source, tersedia sebagai Docker image, dan menjalankan seluruh fungsi tersebut dalam satu container serta satu database. Panduan ini memasangnya pada VPS di balik domain pendek yang sebenarnya, 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 mengendalikannya dari command line.
Nomor versi di sini adalah versi yang berlaku pada Oktober 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 AOutput 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.
File compose
Shlink memerlukan database. SQLite cukup untuk pengujian, tetapi Postgres adalah pilihan yang tepat untuk data yang akan dipertahankan 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 terpasang. Docker menulis aturan penerusan sendiri sebelum firewall host, sehingga baris 8080:8080 biasa dapat mengekspos aplikasi meskipun firewall pada server tampak tertutup. Mengikat port ke alamat loopback mencegah hal tersebut. Pola yang sama berlaku untuk aplikasi apa pun yang Anda jalankan dengan cara ini, dan dijelaskan lebih lanjut dalam panduan Docker Compose pada VPS.
Password database berasal dari file .env di sebelah file compose, sehingga password tersebut tidak 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/.envJalankan dan pantau sampai API aktif.
cd /opt/shlink
sudo docker compose up -d
sudo docker compose logs -f shlinkSaat pertama kali dijalankan, proses ini menjalankan migrasi database sehingga memerlukan waktu lebih lama daripada proses berikutnya. Setelah stabil, periksa apakah service merespons secara lokal.
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/rest/health200 berarti API aktif dan koneksi database berfungsi. 503 berarti Shlink berjalan, tetapi pemeriksaan database gagal. Endpoint health memberikan respons 503 untuk kesalahan database apa pun, bukan 500. Jika curl justru mencetak 000, tidak ada yang merespons pada port tersebut: container berhenti saat pemeriksaan start-up dan Docker sedang me-restart-nya, jadi baca log di atas. Dalam kedua kasus, penyebab yang biasanya terjadi adalah DB_PASSWORD di .env tidak sama dengan yang digunakan saat Postgres dibuat, karena image Postgres hanya membaca POSTGRES_PASSWORD ketika menginisialisasi data directory yang kosong. Mengedit password setelahnya tidak berpengaruh sampai Anda menghapus volume dan menjalankannya kembali.
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.comIS_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 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 hanya menampilkan kunci satu kali. Salin sekarang, karena kunci disimpan dalam bentuk hash dan tidak dapat ditampilkan lagi. shlink api-key:list menampilkan nama dan status aktif setiap kunci, bukan kuncinya. Cabut kunci dengan shlink api-key:disable dan namanya.
Setiap panggilan REST menyertakan kunci dalam header X-Api-Key.
curl -H "X-Api-Key: YOUR_KEY" https://s.example.com/rest/v3/short-urlsObjek JSON dengan kunci shortUrls berarti kunci tersebut berfungsi. 401 dengan type bernilai https://shlink.io/api/error/invalid-api-key dan judul Invalid API key berarti kunci tersebut salah, dinonaktifkan, atau telah melewati tanggal kedaluwarsa.
Membuat short link dari command line
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 docsshort-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.
Web client dan kode QR
Web client kini tersedia di 127.0.0.1:8081 dan memerlukan entri proxy sendiri, atau SSH tunnel jika Anda tidak ingin memublikasikannya. Saat pertama kali dimuat, web client meminta URL server dan kunci API. Masukkan https://s.example.com dan kunci yang Anda buat. Client menyimpan keduanya di penyimpanan browser dan memanggil API Anda secara langsung, sehingga tidak ada data yang melewati pihak lain. Memisahkan antarmuka dari API adalah pola yang perlu diperhatikan, karena pola yang sama memungkinkan Halcyon mengubah tampilan pustaka Jellyfin menjadi toko penyewaan tahun 1990-an tanpa mengubah media server di belakangnya.
Kode QR dibuat oleh web client, bukan API. Shlink 5 menghapus cara lama dengan menambahkan /qr-code ke URL pendek, sehingga alamat tersebut tidak lagi mengembalikan gambar. Sebagai gantinya, web client menggambar kode tersebut di browser Anda tanpa mengirim permintaan ke Shlink. Buka opsi kode QR pada tautan mana pun dalam daftar URL pendek, lalu atur ukuran, format, dan margin, kemudian unduh hasilnya. Naikkan tingkat koreksi error agar kode tetap dapat dipindai saat dicetak dalam ukuran kecil atau tertutup sebagian. Dialog yang sama juga dapat digunakan untuk mengatur warna dan logo.
Pastikan tetap berjalan
Shortener dapat gagal tanpa terlihat. Tautan berhenti melakukan redirect dan tidak ada yang memberi tahu Anda, karena orang yang mengeklik mengira tautan tersebut sudah mati. Arahkan uptime check ke URL pendek yang sebenarnya, bukan ke halaman beranda, lalu buat alert untuk apa pun yang bukan redirect. Instance Uptime Kuma yang di-host sendiri dapat melakukan hal ini dengan baik dan dapat memantau status code 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.gzFile tersebut bersama compose file dapat membangun ulang seluruh service pada server baru. Setiap aplikasi di server memerlukan versinya sendiri dari kedua file tersebut. Pustaka foto merupakan kasus yang lebih rumit, karena PhotoPrism dan Immich menyimpan file asli di disk sekaligus baris data dalam database. Karena itu, dump saja hanya memulihkan akun dan pengaturan, bukan satu pun foto. Proses upgrade adalah sudo docker compose pull lalu sudo docker compose up -d, dan Shlink menjalankan migration baru saat start. Buat dump sebelum melakukan pull, karena migration tidak dapat di-rollback.
FAQ
Mengapa short link saya mengembalikan 404 setelah saya menambahkan reverse proxy?
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.
Bagaimana cara memindahkan Shlink ke server lain?
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.