Yeni yapılandırmayı kaydedip servisi başlatıyorsunuz ve terminal tek satırla cevap veriyor: nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use). Ya da bir Node uygulamasında Error: listen EADDRINUSE: address already in use :::3000. Servis ayağa kalkmıyor, site kapalı ve saat gece yarısı.
Bu noktada refleks hep aynıdır: lsof ile PID'i bul, kill -9 yapıştır, servisi tekrar başlat. Bu çoğu zaman işe yarar ama çoğu zaman aynı zamanda yanlıştır. Çünkü hata size aslında şunu söylüyor: "İstediğin adres ve port ikilisini zaten başka biri dinliyor." O "başka biri" genellikle bir yabancı değil, sizin kendi servisinizin hâlâ çalışan bir kopyası, bir Docker konteyneri veya sadece çekirdeğin birkaç saniyeliğine rezerve tuttuğu bir sokettir. Yanlış süreci -9 ile öldürmek veritabanı bozulmasından yarım kalmış istemci bağlantılarına kadar bir dizi ikincil sorun üretir.
Bu rehberde önce portu kimin tuttuğunu kesin olarak tespit edeceğiz, sonra bulduğunuz şeyin türüne göre — systemd servisi, Docker konteyneri, zombi süreç veya TIME_WAIT durumu — doğru çözümü uygulayacağız. Sonunda bu hatayı bir daha tahminle değil, üç komutluk bir teşhis rutiniyle çözeceksiniz.
Address Already in Use Hatası Tam Olarak Ne Demek?#
Bir sunucu programı çalışmaya başladığında işletim sisteminden bir adres rezervasyonu ister; buna bind() denir. Rezervasyon tek bir port numarası değil, üçlü bir kombinasyondur: protokol (TCP/UDP), IP adresi ve port. 98: Address already in use, bu kombinasyonun zaten kayıtlı olduğu anlamına gelir.
Bu ayrıntı önemlidir çünkü "80 portu dolu" ifadesi çoğu zaman eksiktir. Aynı anda şunlar mümkündür:
127.0.0.1:8080dinleyen bir süreç varken başka bir süreç192.168.1.10:8080dinleyebilir. Çakışma yoktur.- Ancak biri
0.0.0.0:8080(tüm arayüzler) dinliyorsa, artık hiçbir arayüzde 8080 alınamaz. - TCP 53 ile UDP 53 farklı rezervasyonlardır; biri doluyken diğeri boş olabilir.
Hata mesajındaki adresi mutlaka okuyun. bind() to 0.0.0.0:80 ile bind() to 127.0.0.1:80 tamamen farklı iki teşhise götürür. Nginx'te ikisini birden dinleyen bir listen satırı yazdıysanız, çakışma sizin kendi yapılandırma dosyanızda olabilir; bu durumda nginx -t çıktısı duplicate listen options uyarısı verir ve dışarıda süreç aramanıza hiç gerek yoktur.
Yaygın uygulama hata mesajları#
| Uygulama | Tipik mesaj |
|---|---|
| Nginx | bind() to 0.0.0.0:80 failed (98: Address already in use) |
| Apache | (98)Address already in use: AH00072: make_sock: could not bind |
| Node.js | Error: listen EADDRINUSE: address already in use :::3000 |
| Python | OSError: [Errno 98] Address already in use |
| Java / Tomcat | java.net.BindException: Address already in use |
| Docker | Bind for 0.0.0.0:8080 failed: port is already allocated |
| MySQL | Can't start server: Bind on TCP/IP port: Address already in use |
Hepsi aynı çekirdek hatasının farklı dillerdeki ifadesidir; teşhis yöntemi de aynıdır.
Portu Hangi İşlem Tutuyor? ss ile Bulun#
Modern Linux dağıtımlarında ss, eski netstat komutunun yerini almıştır ve çok daha hızlıdır. Bir portu dinleyen süreci bulmanın en doğrudan yolu şudur:
# 80 portunu dinleyen TCP soketini ve sahibi süreci göster
sudo ss -tlnp 'sport = :80'
# Tüm dinlenen portları listele
sudo ss -tulnp
Bayrakların anlamı: -t TCP, -u UDP, -l sadece dinleme durumundakiler, -n isim çözümlemesi yapma (çok daha hızlı), -p süreç bilgisini göster. Süreç bilgisini görebilmek için sudo şarttır; root olmadan users:(("...",pid=...)) sütunu boş gelir ve "hiçbir şey bulamadım" yanılgısına düşersiniz.
Tipik çıktı şöyle görünür:
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=1421,fd=6),("nginx",pid=1420,fd=6))
Burada okumanız gereken üç şey var: Local Address:Port sütunundaki 0.0.0.0 tüm arayüzler demektir; pid=1420 ana süreçtir (küçük PID genellikle parenttir); ve süreç adı nginx'tir, yani aradığınız "yabancı" aslında sizin kendi servisinizin zaten çalışan bir kopyasıdır. Portları listeleme komutlarının ayrıntısı için ss ve netstat ile açık portları listeleme yazısına bakabilirsiniz.
lsof ile alternatif ve tamamlayıcı sorgu#
lsof daha yavaştır ama dosya tanıtıcı düzeyinde bilgi verdiği için bazen ss'in göremediğini gösterir:
# 80 portunu kullanan her şeyi listele
sudo lsof -i :80
# Sadece dinleyenleri, sayısal olarak
sudo lsof -nP -iTCP:80 -sTCP:LISTEN
-nP bayrakları DNS ve servis adı çözümlemesini kapatır; büyük sistemlerde saniyeler kazandırır. Hiçbir şey dönmüyorsa iki ihtimal vardır: ya port gerçekten boştur (o zaman sorun TIME_WAIT veya yapılandırmadır) ya da süreç başka bir ağ ad alanında (network namespace) çalışmaktadır — bu neredeyse her zaman Docker demektir.
PID'den sürecin kimliğine ulaşmak#
Elinizde bir PID varken körlemesine öldürmeden önce onun ne olduğunu öğrenin:
ps -fp 1420 # komut satırı, kullanıcı, başlangıç zamanı
sudo ls -l /proc/1420/cwd # çalıştığı dizin
sudo cat /proc/1420/cmdline | tr '\0' ' ' # tam argümanlar
systemctl status 1420 # hangi systemd servisine ait?
Son komut en değerlisidir: PID bir systemd birimine aitse çıktı size servis adını verir ve o anda ne yapmanız gerektiği netleşir. Süreç inceleme araçlarını daha ayrıntılı kullanmak için ps, top ve htop ile süreç izleme yazısı iyi bir başlangıçtır.
kill -9 Yerine Ne Yapmalısınız?#
Portu tutan şeyi bulduktan sonra doğru hamle, o şeyin ne olduğuna bağlıdır. Aşağıdaki karar tablosu pratikte karşılaşacağınız beş durumu kapsar:
| Bulduğunuz | Doğru çözüm | Yapmayın |
|---|---|---|
| Zaten çalışan aynı servis | systemctl restart nginx | İkinci kopyayı başlatmaya çalışmak |
| Farklı bir systemd servisi | Birini durdurun veya portunu değiştirin | Süreci kill -9 ile öldürmek |
| Docker konteyneri | Port eşlemesini değiştirin | Host'ta PID aramak |
| Kaynak sızdıran zombi süreç | Önce kill (SIGTERM), gerekirse -9 | Doğrudan -9 ile başlamak |
| Hiçbir süreç yok | TIME_WAIT; bekleyin veya SO_REUSEADDR | Sunucuyu yeniden başlatmak |
kill -9 (SIGKILL) sürece hiçbir temizlik şansı vermez: açık dosyalar düzgün kapanmaz, veritabanı tamponları diske yazılmaz, geçici dosyalar ve PID dosyaları ortada kalır. Bir MySQL veya PostgreSQL sürecini böyle öldürmek gerçek veri kaybı riskidir. Doğru sıra her zaman şudur: önce servisin kendi durdurma yolu, sonra kill (SIGTERM), en son çare kill -9. Sinyallerin davranış farkları için süreç sonlandırma ve kill sinyalleri yazısına bakın.
Durum 1: Servis zaten çalışıyor#
En sık karşılaşılan senaryo budur ve genellikle şu şekilde oluşur: yapılandırmayı düzenlediniz, systemctl start nginx yazdınız, ama nginx zaten çalışıyordu. start, çalışan bir servise dokunmaz; ancak siz elle nginx binary'sini çalıştırdıysanız ikinci kopya bind edemez ve hata alırsınız.
systemctl status nginx # gerçekten çalışıyor mu?
sudo systemctl restart nginx # yapılandırmayı yeniden yükle ve başlat
sudo systemctl reload nginx # kesinti istemiyorsanız (nginx destekler)
Nginx ve Apache gibi sunucularda reload, mevcut bağlantıları düşürmeden yeni yapılandırmayı devreye alır. Servis yönetimi mantığının tamamı systemd servis yönetimi yazısında.
Durum 2: Başka bir servis o portu istiyor#
ss çıktısında beklemediğiniz bir program görüyorsanız (örneğin 80 portunda Apache varken Nginx başlatmaya çalışıyorsanız) bir karar vermeniz gerekir. İki servisi aynı portta çalıştıramazsınız; ya birini kapatın ya da mimariyi değiştirin:
# Apache'yi durdur ve açılışta başlamasını engelle
sudo systemctl stop apache2
sudo systemctl disable apache2
# Nginx'i başlat
sudo systemctl start nginx
disable adımını atlamak klasik bir tuzaktır: servis o an durur ama sunucu yeniden başlatıldığında geri gelir ve bu kez hatayı Nginx alır. Sunucu her reboot sonrası açılmıyorsa ilk bakılacak yer burasıdır.
Her iki uygulamayı da çalıştırmanız gerekiyorsa doğru yaklaşım, birini arka plana almak ve önde bir ters vekil sunucu kullanmaktır. Örneğin Apache'yi 8080'e alıp Nginx'i 80'de reverse proxy olarak konumlandırabilirsiniz; Nginx kurulumu ve 502 Bad Gateway hatası yazıları bu senaryonun devamını anlatır.
Durum 3: Portu bir Docker konteyneri tutuyor#
sudo ss -tlnp çıktısında docker-proxy görüyorsanız ya da hiçbir süreç görünmüyor ama port doluysa, sorumlu Docker'dır. Konteynerler kendi ağ ad alanlarında çalıştığı için host tarafında normal bir süreç gibi görünmezler.
# Hangi konteyner hangi portu yayınlıyor?
docker ps --format 'table {{.Names}}\t{{.Ports}}\t{{.Status}}'
# Belirli bir portu arayın
docker ps --filter "publish=8080"
Burada kill -9 kesinlikle yanlış hamledir: docker-proxy sürecini öldürürseniz Docker'ın ağ durumu tutarsız hâle gelir ve konteyner yeniden başlatılana kadar erişilemez kalır. Doğrusu, ya konteyneri durdurmak ya da port eşlemesini değiştirmektir:
docker stop web-uygulama
# Ya da eşlemeyi değiştirip yeniden ayağa kaldırın (host:konteyner)
docker run -d -p 8081:80 --name web-uygulama nginx:alpine
docker compose kullanıyorsanız değişiklik compose.yaml dosyasındadır:
services:
web:
image: nginx:alpine
ports:
- "8081:80" # host portu 8080 yerine 8081
Ardından docker compose up -d ile yeniden oluşturun. Ağ modelinin ayrıntıları için Docker network yapılandırma ve çok servisli kurulumlar için Docker Compose kullanımı yazılarına bakabilirsiniz.
Durum 4: Süreç var ama cevap vermiyor#
Uygulama takılmış, systemctl stop dönmüyor ve port hâlâ dolu. Bu durumda kademeli ilerleyin:
sudo kill 1420 # SIGTERM: nazik kapanma isteği
sleep 5
sudo ss -tlnp 'sport = :80' # gitti mi?
sudo kill -9 1420 # hâlâ duruyorsa son çare
kill -9 sonrası port hemen boşalmayabilir; bu normaldir ve bir sonraki bölümün konusudur. Süreç D durumunda (kesintisiz uyku, genellikle disk I/O beklerken) takılıysa kill -9 bile işe yaramaz — orada tek çözüm I/O'nun tamamlanmasını beklemek veya sunucuyu yeniden başlatmaktır.
Hiçbir Süreç Yok Ama Port Dolu: TIME_WAIT#
En kafa karıştırıcı senaryo budur. ss hiçbir dinleyici göstermiyor, lsof boş dönüyor, ama uygulama hâlâ Address already in use diyor. Sebep neredeyse her zaman TIME_WAIT durumudur.
Bir TCP bağlantısını kapatan taraf, soketi hemen serbest bırakmaz; ağda gecikmiş paketlerin yeni bir bağlantıya karışmaması için soketi belirli bir süre TIME_WAIT durumunda tutar. Linux'ta bu süre sabit 60 saniyedir (net.ipv4.tcp_fin_timeout bu değeri değiştirmez; o parametre FIN_WAIT2 durumu içindir — yaygın bir yanlış bilgidir).
Durumu görmek için:
# 80 portundaki TIME_WAIT soketlerini say
ss -tan 'sport = :80' | grep -c TIME-WAIT
# Tüm durumların özeti
ss -s
Yüzlerce TIME_WAIT görmek sorun değildir; yoğun bir web sunucusunda bu tamamen normaldir ve bu soketler yeni gelen bağlantıları engellemez.
Doğru çözüm: SO_REUSEADDR#
TIME_WAIT yüzünden yeniden başlatılamayan bir uygulamanın çözümü çekirdek ayarı kurcalamak değil, uygulamanın soketi SO_REUSEADDR seçeneğiyle açmasıdır. Bu seçenek çekirdeğe "bu adreste TIME_WAIT durumunda soketler olsa bile bind etmeme izin ver" der. Nginx, Apache ve PostgreSQL gibi olgun sunucular bunu zaten yapar; hatayı genellikle kendi yazdığınız uygulamalarda görürsünüz.
Python'da:
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # bind'dan ÖNCE
s.bind(("0.0.0.0", 8000))
s.listen(128)
Node.js'te net ve http modülleri bunu varsayılan olarak uygular; EADDRINUSE alıyorsanız sorun büyük olasılıkla gerçekten çalışan başka bir süreçtir. Bu yüzden Node'da hatayı gördüğünüzde önce ss ile kontrol edin, TIME_WAIT varsayımına atlamayın.
⚠️ İnternette sık önerilen net.ipv4.tcp_tw_recycle parametresi Linux 4.12 sürümünden itibaren tamamen kaldırılmıştır ve daha önceki sürümlerde de NAT arkasındaki istemcilerin bağlantılarını sessizce bozduğu için zararlıydı. Bugün hâlâ bunu öneren bir rehbere denk gelirseniz o rehberin geri kalanına da güvenmeyin.
1024 Altındaki Portlar ve İzin Karışıklığı#
Bazen Address already in use sandığınız şey aslında Permission denied hatasıdır ve tersi de olur. Linux'ta 1024'ün altındaki portlar ayrıcalıklıdır; root olmayan bir kullanıcı doğrudan bağlanamaz. Bir uygulamayı normal kullanıcıyla 80 portunda başlatmaya çalışırsanız aldığınız hata Address already in use değil Permission denied (13) olur — ama telaş içinde ikisi karıştırılır ve saatlerce boş yere süreç aranır.
Root olarak çalıştırmadan 80 portunu kullanmanın temiz yolu yetenek (capability) atamaktır:
# Bu binary'ye ayrıcalıklı port bağlama yeteneği ver
sudo setcap 'cap_net_bind_service=+ep' /usr/bin/node
# Doğrula
getcap /usr/bin/node
Alternatif olarak uygulamayı 3000 gibi yüksek bir portta çalıştırıp önüne bir ters vekil sunucu koyabilirsiniz. Üretimde tercih edilen yöntem budur; PM2 gibi bir süreç yöneticisiyle birlikte kullanımı PM2 ile Node.js süreç yönetimi yazısında anlatılıyor.
Sunucu Her Yeniden Başladığında Hata Geri Geliyorsa#
Elle çözdüğünüz bir çakışmanın reboot sonrası geri dönmesi, neredeyse her zaman açılışta çalışmaya ayarlanmış ikinci bir birimin varlığına işaret eder. Hangi servislerin açılışta devreye girdiğini görün:
# Açılışta etkin olan tüm birimler
systemctl list-unit-files --state=enabled --type=service
# Belirli bir servisin açılış durumu
systemctl is-enabled apache2
stop bir servisi yalnızca o an durdurur; disable ise açılış bağlantısını kaldırır. İkisini birlikte kullanmadıysanız sorun bir sonraki yeniden başlatmada geri gelir. Socket aktivasyonu kullanan birimlerde ayrıca .socket biriminin de kapatılması gerekir, çünkü portu tutan asıl birim odur:
sudo systemctl disable --now apache2.service
sudo systemctl disable --now apache2.socket 2>/dev/null
İki servisin açılışta yarışması durumunda ise doğru çözüm birini kapatmak yerine sıralama vermektir: birim dosyasına After= ve Requires= yönergeleri ekleyerek hangisinin önce ayağa kalkacağını belirleyebilirsiniz. Bu tür bağımlılık kurgusunu systemd servis yönetimi yazısında ayrıntılı bulabilirsiniz.
Windows'ta Portu Kullanan İşlemi Bulmak#
Sunucunuz Windows ise araçlar farklıdır ama mantık birebir aynıdır. Yönetici yetkisiyle açılmış bir komut isteminde veya PowerShell'de:
# Klasik yöntem: dinleyen soketleri ve PID'lerini listele
netstat -ano | findstr ":8080"
# PID'in hangi programa ait olduğunu öğren
tasklist /FI "PID eq 4"
# PowerShell'in daha okunur karşılığı
Get-NetTCPConnection -LocalPort 8080 -State Listen |
Select-Object LocalAddress, LocalPort, OwningProcess
Windows'ta çok sık karşılaşılan iki özel durum vardır. Birincisi PID 4: bu, çekirdeğin System sürecidir ve 80 ya da 443 portunu tutuyorsa sorumlusu genellikle IIS veya http.sys sürücüsüdür; öldürülemez, ilgili servisi durdurmanız gerekir. İkincisi ise Hyper-V rezerve port aralıklarıdır: Docker Desktop veya WSL2 kurulu sistemlerde işletim sistemi bazı port aralıklarını dinamik olarak kendine ayırır ve hiçbir süreç görünmediği hâlde bind başarısız olur. Rezerve aralıkları şu komutla görebilirsiniz:
netsh interface ipv4 show excludedportrange protocol=tcp
Uygulamanızın portu bu aralıkların içine düşüyorsa yapılacak en pratik şey, port numarasını aralık dışına taşımaktır.
Üç Komutluk Teşhis Rutini#
Hatayı bir daha gördüğünüzde sırayla şunları çalıştırın; üçü de bir dakikanızı almaz ve doğru çözüme tahminsiz ulaştırır:
- Kim dinliyor?
sudo ss -tulnp | grep :8080— çıktı varsa süreç adına ve PID'e bakın. - Bu ne?
systemctl status 1420— bir systemd birimi çıkıyorsa çözümrestartveyastop+disable. - Docker mı? Çıktı boşsa veya
docker-proxygörüyorsanızdocker ps --filter "publish=8080"çalıştırın.
Üçü de boş dönerse elinizde TIME_WAIT veya bir yapılandırma çakışması vardır: ss -tan 'sport = :8080' ile durumları sayın ve Nginx kullanıyorsanız sudo nginx -t ile yapılandırma dosyalarınızdaki çift listen satırlarını kontrol edin.
Bu hatayı kalıcı olarak azaltmanın en etkili yolu port planı yapmaktır: hangi uygulamanın hangi portta çalışacağını bir yerde yazılı tutun ve yeni bir servisi ayağa kaldırmadan önce ss -tulnp ile o portun boş olduğunu doğrulayın. Özellikle birden fazla geliştirme ortamının aynı sunucuda yaşadığı kurulumlarda bu tek alışkanlık, gece yarısı yaşanan panik anlarının büyük bölümünü ortadan kaldırır.
Sıkça Sorulan Sorular#
Portu kullanan işlemi bulamıyorum ama port dolu görünüyor, neden?#
Üç olası neden vardır. Birincisi sudo kullanmamış olabilirsiniz; root olmadan ss ve lsof başka kullanıcılara ait süreçleri göstermez. İkincisi süreç bir Docker konteynerinin içinde, yani ayrı bir ağ ad alanında çalışıyor olabilir. Üçüncüsü de port gerçekten boş olabilir ve gördüğünüz şey 60 saniye içinde kendiliğinden temizlenecek TIME_WAIT soketleridir.
TIME_WAIT durumundaki soketleri hemen temizleyebilir miyim?#
Doğrudan ve güvenli bir yolu yoktur; TIME_WAIT süresi TCP protokolünün doğru çalışması için vardır ve Linux'ta 60 saniyedir. Doğru çözüm bu süreyi kısaltmaya çalışmak değil, uygulamanızın soketi SO_REUSEADDR seçeneğiyle açmasını sağlamaktır. Eskiden önerilen tcp_tw_recycle parametresi ise güncel çekirdeklerde kaldırılmıştır ve kullanılmamalıdır.
kill -9 kullanmak neden sakıncalı?#
SIGKILL sinyali süreç tarafından yakalanamaz, yani uygulama kapanmadan önce hiçbir temizlik yapamaz. Bellekteki veriler diske yazılmaz, açık dosya tanıtıcıları ve kilitler düzgün serbest bırakılmaz, geçici dosyalar ve PID dosyaları ortada kalır. Veritabanı sunucularında bu doğrudan veri bozulması riski taşır. Önce normal kill sinyalini deneyin, birkaç saniye bekleyin, sadece cevap vermezse -9 kullanın.
Nginx 80 portunu açamıyor ama Apache de kurulu değil, ne olabilir?#
En sık sebep nginx'in zaten çalışıyor olmasıdır: yapılandırmayı değiştirdikten sonra elle nginx komutunu çalıştırmak, servis zaten aktifken ikinci bir kopya başlatmaya çalışır. systemctl status nginx ile kontrol edin ve start yerine restart veya reload kullanın. İkinci ihtimal yapılandırma dosyalarınızda aynı adres için birden fazla listen satırı bulunmasıdır; nginx -t bunu açıkça raporlar.
Docker'da port zaten kullanılıyor hatasını nasıl çözerim?#
Önce docker ps ile hangi konteynerin o host portunu yayınladığını bulun. Konteynere artık ihtiyaç yoksa durdurun, ihtiyaç varsa host tarafındaki port numarasını değiştirin: örneğin -p 8080:80 yerine -p 8081:80 kullanın. Konteyner içindeki port aynı kalabilir, değiştirmeniz gereken yalnızca iki noktalı ifadenin sol tarafıdır. Compose kullanıyorsanız düzenlemeyi yaml dosyasındaki ports bölümünde yapın.
Aynı portu iki uygulama birlikte kullanabilir mi?#
Aynı protokol, aynı IP ve aynı port üçlüsünü normal şartlarda yalnızca bir süreç dinleyebilir. Ancak farklı IP adreslerine bağlanırlarsa, örneğin biri 127.0.0.1:8080 diğeri 10.0.0.5:8080 dinlerse çakışma olmaz. Ayrıca SO_REUSEPORT seçeneğiyle açılan soketlerde birden fazla süreç aynı portu paylaşıp yükü bölüşebilir; bu, çok çekirdekli sunucularda bilinçli olarak kullanılan bir tekniktir.