infra
Platform

모듈 맵

[Network] ss/lsof로 포트를 점유한 좀비 프로세스 찾아 강제 종료하기

0 / 35 완료

펼치기
0 / 35 완료0%

Networking · 13 / 35

[Network] ss/lsof로 포트를 점유한 좀비 프로세스 찾아 강제 종료하기

port already in use 에러를 해결하고 포트를 점유한 프로세스를 정리합니다

🚨INCIDENT ALERT
HIGH

개발 서버 배포가 address already in use로 실패했습니다. 급해서 아무 PID나 kill했다가 같은 서버의 다른 테스트 작업까지 끊어버렸습니다.

포트를 잡은 프로세스를 정확히 찾고 안전하게 종료하는 순서가 필요합니다. 강제 종료는 마지막 선택이어야 합니다.

포트 사용 중인 프로세스 강제 종료

서버에서 새 서비스를 시작하려 할 때 address already in use 또는 port already in use 에러가 뜨는 경우가 있습니다. 이 장에서는 포트를 점유한 프로세스를 찾고, 상황에 맞게 종료하는 방법을 다룹니다.


이번 챕터에서 배울 것
  • 1포트 충돌(address already in use) 발생 원인과 소켓 구조
  • 2lsof / ss / fuser로 포트 점유 프로세스 식별
  • 3kill SIGTERM·SIGKILL 시그널 차이와 올바른 종료 순서
  • 4systemd Restart=always 서비스의 포트 충돌 해결
  • 5Docker 컨테이너 포트 점유 해제 방법
  • 6TIME_WAIT 소켓으로 인한 바인딩 실패 대응
실습 환경 준비
포트 점유 프로세스 확인
sudo ss -tulnp | grep :포트번호
테스트용 서버 프로세스 실행
python3 -m http.server 8888 &
lsof로 상세 소켓 정보 확인
sudo lsof -i :8888
systemd 서비스 상태 확인
systemctl list-units --type=service --state=running
💡개념

포트 충돌의 원인과 구조

서비스를 재시작했는데 "Address already in use: 8080"이 납니다. systemctl stop을 해서 프로세스를 내렸는데도 포트가 여전히 잡혀있습니다. 뭔가가 포트를 붙들고 있는데 무엇인지, 왜 stop을 해도 안 죽었는지 알 방법이 없습니다. 포트 충돌의 구조를 알아야 sslsof로 원인을 찾고 올바르게 처리할 수 있습니다.

포트 충돌의 원인과 구조

"port already in use" 에러란

하나의 포트(IP:포트 조합)는 동시에 하나의 프로세스만 바인딩할 수 있습니다. 이미 사용 중인 포트에 새 프로세스가 바인딩하려 하면 OS가 오류를 반환합니다.

Node.js:   Error: listen EADDRINUSE: address already in use :::80
Python:    OSError: [Errno 98] Address already in use
Java:      java.net.BindException: Address already in use
nginx:     bind() to 0.0.0.0:80 failed (98: Address already in use)

포트 충돌이 발생하는 주요 원인

원인설명
이전 프로세스가 살아있음서비스 재시작 시 기존 인스턴스가 종료되지 않음
좀비 소켓 (TIME_WAIT)TCP 연결 종료 후 소켓이 일정 시간 유지됨
다른 서비스 충돌예: nginx와 Apache 둘 다 80 포트 사용 설정
Docker 컨테이너컨테이너가 호스트 포트를 점유 중
systemd 재시작 정책Restart=always로 인해 프로세스가 계속 살아남

포트와 소켓의 관계

프로세스 A (nginx)
    └── 소켓 생성: SOCK_STREAM
        └── bind(0.0.0.0:80)  ← 포트 점유
            └── listen() 상태로 대기

프로세스 B (새 nginx)
    └── 소켓 생성: SOCK_STREAM
        └── bind(0.0.0.0:80) → EADDRINUSE (에러!)

0.0.0.0:80은 "모든 인터페이스의 80번 포트"를 의미합니다. 반면 127.0.0.1:80으로 바인딩된 프로세스가 있어도, 0.0.0.0:80에 바인딩하려 하면 충돌이 발생합니다.

포트 상태 종류

LISTEN    — 포트를 열고 새 연결 대기 중 (점유 중)
ESTABLISHED — 현재 활성 연결 (통신 중)
TIME_WAIT — 연결 종료 후 일정 시간 유지 (보통 60~120초)
CLOSE_WAIT — 원격 측이 연결 종료 (로컬 애플리케이션이 close 안 함)

TIME_WAIT 상태 소켓은 프로세스가 없어도 잠시 포트를 점유합니다.


💡개념

포트 점유 프로세스 확인 도구

포트 충돌을 확인하려 합니다. 어떤 프로세스가 포트를 쓰는지, PID는 무엇인지, 시스템에 따라 lsof가 있는 서버도 있고 ss밖에 없는 서버도 있습니다. 두 도구의 사용법을 알아야 어떤 환경에서든 포트 점유 프로세스를 찾고 처리할 수 있습니다.

1. lsof: 열린 파일/소켓 목록

lsof(list open files)는 프로세스가 열고 있는 파일, 소켓, 포트 정보를 보여줍니다.

로컬 터미널
# 실습 디렉토리 준비
mkdir -p /tmp/networking/part3/exam_13 && cd /tmp/networking/part3/exam_13

# 80번 포트를 사용 중인 프로세스 확인
$ sudo lsof -i :80
COMMAND  PID   USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
nginx   1234   root   6u  IPv4  12345      0t0  TCP *:http (LISTEN)
nginx   1235 nobody   6u  IPv4  12345      0t0  TCP *:http (LISTEN)

# 특정 포트 범위 확인
$ sudo lsof -i :8080-8090

# TCP만 확인
$ sudo lsof -i TCP:80

# 특정 프로세스가 열고 있는 포트 확인
$ sudo lsof -p 1234 -i
🔍실행 후 확인할 것
  • PID포트를 점유한 프로세스 ID를 정확히 확인합니다
  • COMMAND종료 대상이 의도한 프로세스인지 이름을 확인합니다
  • LISTEN 해제종료 후 같은 포트가 더 이상 LISTEN 상태가 아닌지 봅니다

출력 필드 의미:

  • COMMAND: 프로세스 이름
  • PID: 프로세스 ID
  • USER: 실행 사용자
  • FD: 파일 디스크립터
  • NODE NAME: 소켓 주소 (IP:포트)

2. ss: 소켓 상태 조회 (권장)

ssnetstat의 현대적 대체 도구로 더 빠르고 정확합니다.

로컬 터미널
# 80번 포트 상태 확인
$ ss -tulnp | grep :80
tcp   LISTEN 0      128    0.0.0.0:80    0.0.0.0:*    users:(("nginx",pid=1234,fd=6))

# 옵션 설명:
# -t: TCP 소켓
# -u: UDP 소켓
# -l: LISTEN 상태 (대기 중인 포트)
# -n: 이름 해석 없이 숫자로 표시
# -p: 프로세스 정보 포함

# 특정 포트 연결 상태 포함 조회
$ ss -tnp | grep :80

3. fuser: 파일/소켓 사용 프로세스

로컬 터미널
# 80번 TCP 포트를 사용하는 PID 확인
$ fuser 80/tcp
80/tcp:  1234 1235

# 프로세스 정보 포함
$ fuser -v 80/tcp
                     USER        PID ACCESS COMMAND
80/tcp:              root       1234 F.... nginx
                     nobody     1235 F.... nginx

# 확인 없이 즉시 종료
$ sudo fuser -k 80/tcp

# 확인하면서 종료
$ sudo fuser -ki 80/tcp

4. netstat (구형, 참고용)

로컬 터미널
# 80번 포트 확인 (일부 구형 환경)
$ netstat -tlnp | grep :80
tcp   0   0 0.0.0.0:80   0.0.0.0:*   LISTEN   1234/nginx

도구 비교

도구장점단점
ss빠름, 현대적일부 구형 OS 미설치
lsof상세 정보느릴 수 있음
fuser직접 kill 가능정보 제한적
netstat익숙함느림, deprecated

실습 1: 포트 점유 프로세스 식별

목표

포트를 점유한 프로세스를 세 가지 방법으로 확인하고 결과를 비교합니다.

단계

1단계: 테스트용 서버 프로세스 실행 — nc로 간단한 리스닝 서버를 띄웁니다.

로컬 터미널
# Python으로 간단한 HTTP 서버 실행 (8888번 포트)
$ python3 -m http.server 8888 &
[1] 12345
Serving HTTP on 0.0.0.0 port 8888 ...

2단계: lsof로 확인 — 포트를 점유한 프로세스의 PID와 경로를 확인합니다.

로컬 터미널
$ sudo lsof -i :8888
COMMAND   PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
python3 12345 user    3u  IPv4  98765      0t0  TCP *:8888 (LISTEN)

3단계: ss로 확인 — 소켓 상태와 PID를 함께 출력합니다.

로컬 터미널
$ ss -tulnp | grep :8888
tcp   LISTEN 0      5    0.0.0.0:8888  0.0.0.0:*  users:(("python3",pid=12345,fd=3))

4단계: fuser로 확인 — 포트를 직접 지정해 사용 중인 PID를 찾습니다.

로컬 터미널
$ fuser 8888/tcp
8888/tcp:             12345

5단계: PID로 프로세스 상세 정보 확인 — PID로 어떤 프로세스인지 상세 확인합니다.

로컬 터미널
# PID로 프로세스 정보 조회
$ ps aux | grep 12345
user  12345  0.0  0.1  15000  8000 pts/0  S  10:30  0:00 python3 -m http.server 8888

# 프로세스 실행 경로 확인
$ ls -la /proc/12345/exe
lrwxrwxrwx 1 user user 0 Mar 27 10:30 /proc/12345/exe -> /usr/bin/python3

6단계: 테스트 서버 종료 — 실습 후 nc 프로세스를 정리합니다.

위험 명령어

이 명령은 프로세스를 종료해 연결 중인 사용자나 배치 작업을 중단시킬 수 있습니다. PID와 프로세스 이름이 목표 서비스인지 확인한 뒤 실행하세요.

로컬 터미널
$ kill %1   # 백그라운드 작업 번호로 종료
# 또는
$ kill 12345

정리: 포트 확인 원라이너

로컬 터미널
# 가장 간결한 방법
$ sudo ss -tulnp | grep :포트번호

# PID만 빠르게 확인
$ fuser 포트번호/tcp

실습 2: 포트를 점유한 프로세스 종료

목표

포트를 점유한 프로세스를 상황별로 올바르게 종료합니다.

단계

1단계: 테스트 환경 준비 — 종료 연습용 프로세스를 두 개 띄웁니다.

로컬 터미널
# 두 개의 서버 프로세스를 다른 포트로 실행
$ python3 -m http.server 9001 &
$ python3 -m http.server 9002 &

$ ss -tulnp | grep '900[12]'
tcp LISTEN 0 5  0.0.0.0:9001  0.0.0.0:*  users:(("python3",pid=11111,fd=3))
tcp LISTEN 0 5  0.0.0.0:9002  0.0.0.0:*  users:(("python3",pid=11112,fd=3))

2단계: kill 명령어로 PID 종료 — SIGTERM(15)으로 정상 종료를 먼저 시도합니다.

위험 명령어

이 명령은 프로세스를 종료해 연결 중인 사용자나 배치 작업을 중단시킬 수 있습니다. PID와 프로세스 이름이 목표 서비스인지 확인한 뒤 실행하세요.

로컬 터미널
# PID 확인 후 종료
$ PID=$(ss -tulnp | grep :9001 | grep -oP 'pid=\K[0-9]+')
$ echo "종료할 PID: $PID"
$ kill $PID    # SIGTERM (정상 종료 요청)

# 응답 없을 때 강제 종료
$ kill -9 $PID   # SIGKILL (즉시 강제 종료)

3단계: fuser -k로 포트 직접 종료 — PID를 몰라도 포트 번호만으로 종료할 수 있습니다.

로컬 터미널
# 포트 번호로 직접 종료 (PID 확인 불필요)
$ sudo fuser -k 9002/tcp

# 확인
$ ss -tulnp | grep :9002
# 아무것도 출력되지 않으면 성공

4단계: kill -9 시그널 차이 이해 — SIGKILL은 프로세스가 무시할 수 없는 강제 종료입니다.

위험 명령어

이 명령은 프로세스를 종료해 연결 중인 사용자나 배치 작업을 중단시킬 수 있습니다. PID와 프로세스 이름이 목표 서비스인지 확인한 뒤 실행하세요.

로컬 터미널
# SIGTERM (15): 정상 종료 요청, 프로세스가 정리 작업 수행 가능
$ kill -15 [PID]   # 또는 kill [PID]

# SIGKILL (9): 즉시 강제 종료, OS가 직접 처리
$ kill -9 [PID]

# 실무 권장 순서
$ kill [PID]        # 먼저 SIGTERM 시도
$ sleep 5           # 5초 대기
$ kill -9 [PID]     # 응답 없으면 SIGKILL

5단계: 종료 후 포트 확인 — 포트가 해제됐는지 ss로 확인합니다.

로컬 터미널
$ ss -tulnp | grep '900[12]'
# 아무것도 출력되지 않으면 포트 해제 완료

실습 3: systemd 서비스의 포트 충돌 해결

목표

Restart=always 설정으로 인해 kill해도 살아나는 서비스를 올바르게 중지합니다.

systemd Restart 정책 이해

로컬 터미널
# 서비스 설정 파일 예시
$ cat /etc/systemd/system/myapp.service
[Unit]
Description=My Application

[Service]
ExecStart=/usr/bin/node /opt/myapp/server.js
Restart=always          # 어떤 이유로든 종료되면 재시작
RestartSec=3            # 3초 후 재시작

[Install]
WantedBy=multi-user.target

단계

1단계: 서비스 상태 확인 — systemctl로 nginx가 실제로 실행 중인지 확인합니다.

로컬 터미널
# 서비스가 포트를 점유 중인지 확인
$ sudo ss -tulnp | grep :3000
tcp LISTEN 0 128  0.0.0.0:3000  0.0.0.0:*  users:(("node",pid=5678,fd=15))

# 해당 PID의 서비스 확인
$ systemctl status $(ps -o unit= -p 5678)
● myapp.service - My Application
   Loaded: loaded (/etc/systemd/system/myapp.service; enabled)
   Active: active (running)

2단계: kill -9로 시도 (실패 케이스) — systemd 관리 서비스는 kill로 종료해도 즉시 재시작됩니다.

위험 명령어

이 명령은 프로세스를 종료해 연결 중인 사용자나 배치 작업을 중단시킬 수 있습니다. PID와 프로세스 이름이 목표 서비스인지 확인한 뒤 실행하세요.

로컬 터미널
$ sudo kill -9 5678
# 3초 후 확인
$ sleep 4 && sudo ss -tulnp | grep :3000
tcp LISTEN 0 128  0.0.0.0:3000  0.0.0.0:*  users:(("node",pid=5901,fd=15))
# PID가 바뀐 채로 다시 살아남!

3단계: systemctl stop으로 올바르게 중지 — systemd가 관리하는 서비스는 반드시 systemctl로 중지합니다.

위험 명령어

이 명령은 실행 중인 서비스 상태를 바꿔 순간적인 중단이나 설정 반영 실패를 만들 수 있습니다. 운영 트래픽 영향과 재시작 후 확인 명령을 먼저 준비하세요.

로컬 터미널
# systemd를 통해 서비스 중지 (재시작 정책 무시)
$ sudo systemctl stop myapp.service

# 확인
$ sudo ss -tulnp | grep :3000
# 출력 없음 — 포트 해제됨

# 서비스 상태 확인
$ systemctl status myapp.service
● myapp.service - My Application
   Active: inactive (dead)

4단계: 서비스 비활성화 (부팅 시 자동 시작 제거) — disable하면 재부팅 후 자동 시작을 막습니다.

위험 명령어

이 명령은 실행 중인 서비스 상태를 바꿔 순간적인 중단이나 설정 반영 실패를 만들 수 있습니다. 운영 트래픽 영향과 재시작 후 확인 명령을 먼저 준비하세요.

로컬 터미널
# 중지 + 부팅 자동 시작 해제
$ sudo systemctl stop myapp.service
$ sudo systemctl disable myapp.service

Docker 컨테이너 포트 점유 해결

Docker 포트 포워딩 충돌과 대화형 모니터링 연계

SRE는 프로세스가 점유한 포트를 강제 해제할 때, 일반 OS 데몬뿐 아니라 커널 레벨에서 Docker 엔진이 기동시킨 포트 바인딩도 함께 통제해야 합니다.

  1. Docker 컨테이너 포트 점유 해소: Docker는 컨테이너 실행 시 호스트 커널의 iptables NAT 테이블 체인을 직접 제어하여 포트를 통제합니다. 따라서 일반적인 kill -9로 프로세스가 죽지 않는다면, docker stop <컨테이너명>을 실행하거나 필요시 iptables -D 명령어를 통해 커널에 주입된 해당 포워딩 규칙을 명시적으로 제거해야 할 수 있습니다.
  2. 대화형 리소스 모니터링 도구 활용: 단순히 포트를 점유한 단일 PID만 죽이기 어려울 경우, htop과 같은 대화형 프로세스 뷰어를 실행하여 트리 구조로 정렬된 부모-자식 프로세스 계보를 직관적으로 추적해 연관 데몬들을 안전하게 원터치 일괄 종료할 수 있습니다.

Docker 포트 포워딩 충돌과 대화형 모니터링 연계

SRE는 프로세스가 점유한 포트를 강제 해제할 때, 일반 OS 데몬뿐 아니라 커널 레벨에서 Docker 엔진이 기동시킨 포트 바인딩도 함께 통제해야 합니다.

  1. Docker 컨테이너 포트 점유 해소: Docker는 컨테이너 실행 시 호스트 커널의 iptables NAT 테이블 체인을 직접 제어하여 포트를 통제합니다. 따라서 일반적인 kill -9로 프로세스가 죽지 않는다면, docker stop <컨테이너명>을 실행하거나 필요시 iptables -D 명령어를 통해 커널에 주입된 해당 포워딩 규칙을 명시적으로 제거해야 할 수 있습니다.
  2. 대화형 리소스 모니터링 도구 활용: 단순히 포트를 점유한 단일 PID만 죽이기 어려울 경우, htop과 같은 대화형 프로세스 뷰어를 실행하여 트리 구조로 정렬된 부모-자식 프로세스 계보를 직관적으로 추적해 연관 데몬들을 안전하게 원터치 일괄 종료할 수 있습니다.

Docker 포트 포워딩 충돌과 대화형 모니터링 연계

SRE는 프로세스가 점유한 포트를 강제 해제할 때, 일반 OS 데몬뿐 아니라 커널 레벨에서 Docker 엔진이 기동시킨 포트 바인딩도 함께 통제해야 합니다.

  1. Docker 컨테이너 포트 점유 해소: Docker는 컨테이너 실행 시 호스트 커널의 iptables NAT 테이블 체인을 직접 제어하여 포트를 통제합니다. 따라서 일반적인 kill -9로 프로세스가 죽지 않는다면, docker stop <컨테이너명>을 실행하거나 필요시 iptables -D 명령어를 통해 커널에 주입된 해당 포워딩 규칙을 명시적으로 제거해야 할 수 있습니다.
  2. 대화형 리소스 모니터링 도구 활용: 단순히 포트를 점유한 단일 PID만 죽이기 어려울 경우, htop과 같은 대화형 프로세스 뷰어를 실행하여 트리 구조로 정렬된 부모-자식 프로세스 계보를 직관적으로 추적해 연관 데몬들을 안전하게 원터치 일괄 종료할 수 있습니다.
로컬 터미널
# Docker가 포트를 점유 중인 경우
$ sudo ss -tulnp | grep :80
tcp LISTEN 0 128  0.0.0.0:80  0.0.0.0:*  users:(("docker-proxy",pid=7890,fd=4))

# 어떤 컨테이너인지 확인
$ docker ps | grep "0.0.0.0:80"
a1b2c3d4e5f6  nginx:latest  "/docker-entrypoint…"  Up 2 hours  0.0.0.0:80->80/tcp

# 컨테이너 중지
$ docker stop a1b2c3d4e5f6

# 확인
$ sudo ss -tulnp | grep :80
# 출력 없음

현상

포트를 점유한 프로세스를 kill -9로 종료했는데 수 초 후 다시 살아납니다.

위험 명령어

이 명령은 프로세스를 종료해 연결 중인 사용자나 배치 작업을 중단시킬 수 있습니다. PID와 프로세스 이름이 목표 서비스인지 확인한 뒤 실행하세요.

로컬 터미널
$ sudo kill -9 $(fuser 8080/tcp)
$ sleep 3
$ fuser 8080/tcp
8080/tcp:  9999   # 다른 PID로 다시 살아남!

원인 분석

  1. systemd Restart=always: 가장 흔한 원인. systemd가 프로세스를 자동 재시작
  2. supervisor/pm2 관리: 프로세스 관리 도구가 재시작
  3. crontab: 주기적으로 프로세스를 실행하는 크론 작업
  4. init.d 스크립트: 구형 SysV init 방식의 모니터링

원인 파악 및 해결

위험 명령어

이 명령은 실행 중인 서비스 상태를 바꿔 순간적인 중단이나 설정 반영 실패를 만들 수 있습니다. 운영 트래픽 영향과 재시작 후 확인 명령을 먼저 준비하세요.

로컬 터미널
# 1. systemd 서비스인지 확인
$ systemctl list-units --type=service --state=running | grep -i 해당서비스명

# systemd 서비스면
$ sudo systemctl stop 서비스명

# 2. supervisor로 관리되는지 확인
$ sudo supervisorctl status

# supervisor 프로세스면
$ sudo supervisorctl stop 프로그램명

# 3. pm2로 관리되는지 확인 (Node.js)
$ pm2 list

# pm2 프로세스면
$ pm2 stop 앱이름

# 4. crontab 확인
$ sudo crontab -l
$ crontab -l   # 현재 사용자

완전 종료 확인 방법

위험 명령어

이 명령은 실행 중인 서비스 상태를 바꿔 순간적인 중단이나 설정 반영 실패를 만들 수 있습니다. 운영 트래픽 영향과 재시작 후 확인 명령을 먼저 준비하세요.

로컬 터미널
# 종료 후 5초 대기하며 재확인
$ sudo systemctl stop myapp
$ for i in {1..5}; do
    echo "[$i초] 포트 상태:"
    ss -tulnp | grep :8080 || echo "포트 해제됨"
    sleep 1
  done

현상

기존 프로세스를 종료했는데도 새 서비스 시작 시 여전히 포트 충돌이 발생합니다.

위험 명령어

이 명령은 실행 중인 서비스 상태를 바꿔 순간적인 중단이나 설정 반영 실패를 만들 수 있습니다. 운영 트래픽 영향과 재시작 후 확인 명령을 먼저 준비하세요.

로컬 터미널
$ sudo systemctl stop nginx
$ sudo systemctl start myapp
Job for myapp.service failed. See 'journalctl -xe'
# Error: bind EADDRINUSE: address already in use :::80

원인 분석

TIME_WAIT 소켓

TCP 연결 종료 후 소켓은 TIME_WAIT 상태로 일정 시간(기본 60초) 유지됩니다.

로컬 터미널
$ ss -tn | grep TIME-WAIT | grep :80
TIME-WAIT  0  0  192.168.1.50:80  10.0.0.1:54321

SO_REUSEADDR 미설정

애플리케이션이 SO_REUSEADDR 소켓 옵션을 설정하지 않으면 TIME_WAIT 상태의 소켓과 충돌할 수 있습니다.

해결 방법

위험 명령어

이 명령은 실행 중인 서비스 상태를 바꿔 순간적인 중단이나 설정 반영 실패를 만들 수 있습니다. 운영 트래픽 영향과 재시작 후 확인 명령을 먼저 준비하세요.

로컬 터미널
# 방법 1: 잠시 대기 (TIME_WAIT 소멸 기다림)
$ sleep 60 && sudo systemctl start myapp

# 방법 2: 커널 파라미터로 TIME_WAIT 재사용 허용
$ sudo sysctl -w net.ipv4.tcp_tw_reuse=1

# 방법 3: ss로 TIME_WAIT 상태 소켓 모니터링
$ watch -n 1 'ss -tn | grep TIME-WAIT | grep :80'

# 방법 4: 남아있는 프로세스 최종 확인
$ sudo lsof -i :80
$ sudo ss -tulnp | grep :80
$ fuser 80/tcp

예방 방법

  • 애플리케이션 코드에 SO_REUSEADDR 옵션 설정 (개발자에게 요청)
  • 서비스 중지 후 최소 30초 대기 후 재시작
  • systemd 서비스에 TimeoutStopSec 설정으로 충분한 종료 시간 확보

💼
실무 맥락
현업 패턴

배포 시 포트 충돌 시나리오

새 버전 배포 과정에서 포트 충돌은 흔히 발생합니다. 현업에서는 다음과 같은 패턴으로 처리합니다.

표준 대응 절차

위험 명령어

이 명령은 실행 중인 서비스 상태를 바꿔 순간적인 중단이나 설정 반영 실패를 만들 수 있습니다. 운영 트래픽 영향과 재시작 후 확인 명령을 먼저 준비하세요.

로컬 터미널
# 1. 포트 점유 상태 확인
$ sudo ss -tulnp | grep :8080

# 2. 점유 프로세스가 서비스인지 확인
$ systemctl status $(sudo ss -tulnp | grep :8080 | grep -oP '"[^"]+",pid=\K[0-9]+' | xargs -I{} ps -o unit= -p {})

# 3. 서비스면 systemctl stop, 아니면 kill
# (서비스)
$ sudo systemctl stop old-service

# (일반 프로세스)
$ sudo kill -TERM [PID]
$ sleep 5
$ sudo kill -9 [PID] 2>/dev/null || true

# 4. 포트 해제 확인
$ sudo ss -tulnp | grep :8080 || echo "포트 해제됨"

# 5. 새 서비스 시작
$ sudo systemctl start new-service

무중단 배포와의 관계

포트 충돌 없이 서비스를 교체하려면:

방법 1: Blue-Green 배포
  - 기존: 8080 포트에서 실행
  - 새 버전: 8081 포트에서 먼저 시작
  - 로드밸런서에서 트래픽을 8081로 전환
  - 기존 8080 서비스 중지

방법 2: systemd socket activation
  - systemd가 소켓을 관리
  - 서비스 재시작 시 소켓은 유지
  - 포트 충돌 없이 교체 가능

포트 현황 모니터링

로컬 터미널
# 시스템 전체 포트 현황 스냅샷
$ sudo ss -tulnp | awk 'NR>1 {print $5, $7}' | sort -k1

# 특정 사용자가 열고 있는 포트
$ sudo lsof -u www-data -i | grep LISTEN

# 포트별 프로세스 매핑 저장
$ sudo ss -tulnp > /var/log/port-snapshot-$(date +%Y%m%d-%H%M%S).txt

알아두어야 할 포트 범위

범위이름설명
1-1023Well-knownroot 권한 필요 (HTTP:80, HTTPS:443)
1024-49151Registered일반 사용자 가능 (8080, 3000 등)
49152-65535Dynamic/EphemeralOS가 클라이언트 연결에 자동 할당

: 애플리케이션은 가능하면 1024 이상 포트를 사용하세요. root 권한 없이 실행 가능하고, 보안상으로도 더 안전합니다.


핵심 명령어 치트시트

위험 명령어

이 명령은 실행 중인 서비스 상태를 바꿔 순간적인 중단이나 설정 반영 실패를 만들 수 있습니다. 운영 트래픽 영향과 재시작 후 확인 명령을 먼저 준비하세요.

로컬 터미널
# ===== 포트 점유 확인 =====
sudo ss -tulnp | grep :포트번호          # 권장
sudo lsof -i :포트번호                   # 상세 정보
fuser 포트번호/tcp                       # PID만 빠르게

# ===== 프로세스 종료 =====
kill [PID]                              # SIGTERM (정상 종료)
kill -9 [PID]                           # SIGKILL (강제 종료)
sudo fuser -k 포트번호/tcp               # 포트로 직접 종료

# ===== systemd 서비스 =====
sudo systemctl stop 서비스명             # 서비스 중지
sudo systemctl disable 서비스명          # 자동 시작 해제

# ===== Docker =====
docker ps | grep 포트번호                # 어떤 컨테이너인지 확인
docker stop 컨테이너ID                   # 컨테이너 중지

정리

포트 충돌 해결의 핵심은 무엇이 포트를 점유하고 있는지 정확히 파악한 뒤, 그 프로세스의 관리 방식에 맞게 종료하는 것입니다. 단순히 kill -9를 남발하면 systemd나 supervisor에 의해 즉시 재시작될 수 있습니다. 서비스 관리 도구를 통해 올바르게 중지하는 습관을 들이세요.

지식 확인

퀴즈 — 5문제

Q1

`lsof -i :80` 명령어의 역할로 올바른 것은?

Q2

systemd에서 `Restart=always`로 설정된 서비스를 `kill -9`로 종료하면 어떻게 되는가?

Q3

`fuser -k 80/tcp` 명령어의 동작 방식은?

Q4

Docker 컨테이너가 80번 포트를 점유한 경우 올바른 해결 방법은?

Q5

`ss -tulnp | grep :8080` 출력에서 확인할 수 있는 정보가 아닌 것은?

0 / 5 답변

🧪 실습으로 확인하기

포트는 열렸다는데 왜 안 되지? — ss/netstat/telnet으로 TCP 진단

초급

"포트 8080 열었는데요?"와 "왜 안 돼요?" 사이의 간극을 메우는 실습. ss로 바인딩 상태를 확인하고, telnet/nc으로 원격 연결을 테스트하고, iptables 방화벽을 진단하고, 바인딩 주소(0.0.0.0 vs 127.0.0.1)까지 수정하는 4단계 TCP 포트 진단 플로우를 완성한다.

35📋 4단계💻 직접 환경
실습 시작하기 →

이것도 배워보세요

networking입문 · 45
[Network] curl 명령어로 HTTP 상태 코드 및 헤더 분석하기
Networking 트랙 계속
docker입문 · 30
[Docker] 백엔드 개발자에게 Docker와 컨테이너 가상화가 필수인 이유
Docker 트랙 시작점