SSD Nodes Learn Hosting plans →
Mga Gabay Matt ConnorNi Matt Connor · Na-update 2026-08-31

Self-hosted URL Shortener gamit ang Shlink at Docker

Magpatakbo ng sariling URL shortener sa VPS gamit ang Shlink 5.1 at Docker Compose, mula DNS at Postgres hanggang API keys, QR codes, web client, at click stats.

Ano ang binubuo mo

Ang self-hosted URL shortener ay isang maliit na server na nagko-convert ng mahabang link sa maikling link na pagmamay-ari mo, at binibilang ang bawat click dito. Shlink ang dapat piliin: open source ito, inilalabas bilang Docker image, at ginagawa nito ang buong trabaho sa isang container kasama ang isang database. Inilalagay ng gabay na ito ang Shlink sa isang VPS sa likod ng aktuwal na short domain, na may HTTPS, API key, QR code, at click statistics.

Dalawang bahagi ang nagbibigay rito ng functionality na karaniwang makikita sa commercial shortener. Sumasagot ang API server sa mga redirect at nag-iimbak ng data. Ang web client ay hiwalay na static app na kumokonekta sa API mula sa browser mo. Maaari mong patakbuhin ang dalawa, o API lang at kontrolin ito mula sa command line.

Ang mga version number dito ay ang kasalukuyang version noong July 2026: Shlink 5.1 at shlink-web-client 4.8.

Ituro muna ang maikling domain sa server

Ang domain ang ginagamit na pangalan ng serbisyo. Ang s.example.com/abc123 ang link na nakikita ng mga tao, kaya pumili ng maikling pangalan bago mag-install ng anuman. Itinatago ng Shlink ang domain kasama ng bawat short URL. Kapag binago mo ito sa ibang pagkakataon, hindi na gagana ang lahat ng link na naibigay mo na.

Gumawa ng isang DNS A record para sa maikling domain at ituro ito sa public IPv4 address ng iyong VPS. Magdagdag din ng AAAA record kung may IPv6 ang server. Pagkatapos, tiyaking nagre-resolve ito bago magpatuloy.

dig +short s.example.com A

Dapat ang output ay ang address ng iyong server. Kung walang output, hindi pa propagated ang record. Mabibigo ang bawat susunod na hakbang sa paraang mahirap i-diagnose, dahil hindi maaaring mag-issue ng TLS (transport layer security) certificate para sa pangalang hindi nagre-resolve.

Ang compose file

Kailangan ng database ng Shlink. Gumagana ang SQLite para sa test, pero Postgres ang tamang piliin para sa anumang balak mong panatilihin, dahil naiipon ang mga row ng pagbisita at mas mahusay pangasiwaan ng Postgres ang mga index at sabay-sabay na pagsusulat. Ilagay ito sa /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:

Naka-bind ang parehong published port sa 127.0.0.1, kaya walang maaabot mula sa internet hangga't hindi naka-set up ang reverse proxy sa susunod na seksyon. Nauuna ang sariling forwarding rules ng Docker kaysa sa host firewall, kaya maaaring ilantad ng isang plain 8080:8080 line ang app kahit mukhang sarado ang firewall ng server. Iniiwasan ito ng pag-bind sa loopback address. Nalalapat din ang parehong pattern sa anumang app na patatakbuhin mo sa ganitong paraan, at mas detalyado itong ipinaliwanag sa gabay sa Docker Compose sa isang VPS.

Galing sa isang .env file sa tabi ng compose file ang database password, kaya hindi ito napupunta sa 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

Simulan ito at i-monitor habang umaandar ang API.

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

Sa unang pagsisimula, pinapatakbo ang database migrations, kaya mas matagal ito kaysa sa mga susunod na pagsisimula. Kapag naging stable na ito, tingnan kung sumasagot nang lokal ang service.

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

Ibig sabihin ng 200 ay gumagana ang API at ang database connection. Ang 500 dito ay halos palaging database ang sanhi: hindi tugma ang DB_PASSWORD sa .env sa ginamit noong ginawa ang Postgres, dahil binabasa lang ng Postgres image ang POSTGRES_PASSWORD kapag ini-initialize nito ang isang walang laman na data directory. Walang epekto ang pag-edit ng password sa kalaunan hanggang sa alisin mo ang volume at simulan itong muli.

Tapusin ang HTTPS sa harap nito

Naghahatid ang Shlink ng plain HTTP sa port 8080. Sa reverse proxy dapat ilagay ang TLS, at ang pinakamahalagang setting ay ang pagpapasa ng orihinal na host name. Tinutukoy ng Shlink kung saang domain kabilang ang isang short code sa pamamagitan ng pagbasa sa Host header. Kaya kapag binago ito ng proxy, nagbabalik ito ng 404 response para sa mga link na umiiral, at napupunta sa maling domain ang mga visit stats.

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

Pagkatapos, mag-issue ng certificate. Nasa guide ng Certbot para sa nginx sa Ubuntu 24.04 ang buong walkthrough, kasama ang renewal timer.

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

Ang IS_HTTPS_ENABLED: "true" sa compose file ang nagiging sanhi para ilagay ng Shlink ang https:// sa mga short URL na ibinabalik nito. Hindi nito awtomatikong pinapagana ang TLS. Panatilihin itong false sa likod ng HTTPS proxy, at bawat link na ibinabalik ng API ay magiging http:// link na magre-redirect pa. Nagdadagdag ito ng isang round trip at mukhang mali sa web client.

Gumawa ng API key

Walang makaka-access sa API nang walang key. Gumawa ng isa gamit ang CLI sa loob ng container.

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

Isang beses lang ipinapakita ng command ang key. Kopyahin ito agad dahil naka-store ito bilang hash at hindi na maaaring ipakita muli. Ipinapakita ng shlink api-key:list ang mga pangalan at kung enabled ang bawat key, pero hindi ang mismong key. I-revoke ang isang key gamit ang shlink api-key:disable at ang pangalan nito.

Dala ng bawat REST call ang key sa isang X-Api-Key header.

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

Nangangahulugan ang JSON object na may shortUrls key na gumagana ang key. Ang 401 na may INVALID_API_KEY ay nangangahulugang mali, disabled, o expired na ang key.

Ang CLI ang pinakamabilis na paraan para gumawa ng mga link, at madali itong gamitin sa mga 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

Nagbibigay ang --custom-slug ng nababasang link sa halip na awtomatikong nabuong code. Natatangi ang mga slug sa bawat domain, kaya mabibigo ang ikalawang pagtatangka sa slug na ginagamit na sa halip na tahimik na ma-overwrite ang unang link. Maaaring ulitin ang --tag, at ginagamit ang mga tag para pagsama-samahin ang mga link na kakailanganin mo ng pinagsamang statistics sa ibang pagkakataon.

Ilista muna ang mga mayroon, pagkatapos ay tingnan ang traffic ng isang link.

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

Nagpi-print ang short-url:visits ng isang row para sa bawat click, kasama ang petsa, referrer, at user agent. Mananatiling walang laman ang mga column para sa bansa at lungsod maliban kung magtakda ka ng GEOLITE_LICENSE_KEY environment variable. Isa itong libreng MaxMind key na ginagamit ng Shlink para i-download ang GeoLite2 database. Kung wala ito, itinatala pa rin ang mga pagbisita, ngunit hindi tinutukoy ang kanilang lokasyon.

Ang web client at mga QR code

Nasa 127.0.0.1:8081 na ang web client at kailangan nito ng sarili nitong proxy entry, o SSH tunnel kung ayaw mo itong i-publish. Humihingi ito ng server URL at API key sa unang pag-load. Ilagay ang https://s.example.com at ang key na ginawa mo. Iniimbak ng client ang dalawang ito sa browser storage at direktang tumatawag sa iyong API, kaya walang data na dumadaan sa ibang partido. Isang pattern na dapat pansinin ang paghihiwalay ng interface mula sa API, dahil ito rin ang nagpapahintulot sa Halcyon na gawing 1990s rental store ang isang Jellyfin library nang hindi binabago ang media server sa likod nito.

Walang kailangang i-configure para sa mga QR code. Idagdag ang /qr-code sa anumang maikling URL at ibabalik ng API ang image.

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

Ang size ay ang lapad sa pixels at tumatanggap ng 50 hanggang 1000, na may default na 300. Ang format ay png o svg. Ang margin ay ang bakanteng espasyo sa paligid ng code, sa pixels, at ang kabuuang sukat ng image ay ang size dagdag ang dalawang beses ng margin. Idagdag ang errorCorrection=Q para sa code na nababasa pa rin kapag maliit ang print o bahagyang natatakpan.

Panatilihin itong tumatakbo

Tahimik na pumapalya ang shortener. Tumitigil ang pag-redirect ng mga link at walang nagsasabi sa iyo, dahil ipinapalagay ng taong nag-click na sira na ang link. Ituro ang uptime check sa aktuwal na short URL, hindi sa home page, at magpadala ng alert para sa anumang response na hindi redirect. Mahusay itong ginagawa ng self-hosted na Uptime Kuma instance, at maaari itong mag-monitor ng partikular na status code.

I-back up ang database, hindi ang container. Isang command ang nagda-dump nito.

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

Gamit ang file na iyon at ang compose file mo, mabubuo muli ang buong serbisyo sa bagong server. Kailangan ng bawat app sa server ng sarili nitong bersyon ng pares na iyon. Ang photo library ang mahirap na kaso, dahil parehong nag-iimbak ang PhotoPrism at Immich ng mga original sa disk at ng mga row sa database. Kaya walang nare-restore ang dump lamang. Ang mga upgrade ay sudo docker compose pull na sinusundan ng sudo docker compose up -d, at awtomatikong pinapatakbo ng Shlink ang anumang bagong migration sa pagsisimula. Kunin ang dump bago mag-pull, dahil hindi maaaring i-rollback ang migration.

FAQ

Itinutugma ng Shlink ang isang short code sa domain na nasa Host header. Kung sariling pangalan o internal address ang ipinapadala ng proxy, hahanapin ng Shlink ang code sa domain na walang mga link. Kaya 404 ang ibinabalik nito. Itakda ang proxy_set_header Host $host; sa nginx location block, pagkatapos ay i-reload ang proxy. Agad na gagana ang mga link nang hindi nire-restart ang container.

Kailangan ko ba ng Postgres, o sapat na ang SQLite?

Ayos ang SQLite para subukan ang Shlink at hindi nito kailangan ng pangalawang container. Lumipat sa Postgres bago mo i-publish ang mahahalagang link, dahil nadaragdagan ang visit rows sa bawat click at sina-serialize ng SQLite ang writes. Ang paglipat sa hinaharap ay nangangailangan ng pag-export at pag-import muli ng mga link. Kaya kung Postgres ang pipiliin sa simula, maiiwasan mo ang migration na iyon.

Maaari ko bang mabawi ang API key na nakalimutan kong kopyahin?

Hindi. Hash ng key ang sine-save ng Shlink, kaya ipinapakita lamang ng api-key:list ang mga pangalan at status, hindi ang value. Gumawa ng kapalit gamit ang shlink api-key:generate, i-paste ito sa web client, pagkatapos ay i-disable ang luma gamit ang shlink api-key:disable upang hindi na ito gumana.

Bakit walang laman ang mga country column sa visit stats ko?

Kailangan ng geolocation ang GeoLite2 database. Dina-download lamang ito ng Shlink kapag binigyan mo ito ng GEOLITE_LICENSE_KEY. Libre ang key mula sa MaxMind. Idagdag ito sa environment section, gawin muli ang container, at matutukoy ang lokasyon ng mga bagong visit. Mananatiling blangko ang mga visit na naitala bago nito hanggang patakbuhin mo ang shlink visit:locate.

Panatilihin ang domain at ilipat ang data. I-dump ang database gamit ang pg_dump, kopyahin ang dump at compose file sa bagong server, simulan ang stack, pagkatapos ay i-restore ang dump sa walang-lamang database bago dumating ang aktuwal na traffic. Huling baguhin ang DNS record. Mananatili ang mga short code at visit history ng mga ito dahil nasa database ang lahat ng data.

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