서비스 · 프로세스 관리 37 / 50 · Part 4. 로그·스케줄링·운영
실습 환경: Rocky Linux 9.8 · Ubuntu 24.04.5 (systemd로 부팅한 Docker 격리 컨테이너, 테스트 계정 analyst)

1. 들어가며

16편에서 Ubuntu의 22번 포트를 sshd와 PID 1(systemd) 이 함께 쥐고 있는 것을 보았다. 22편에서는 그 이유가 ssh.socket이라는 unit이라는 것까지 확인했다. 이 구조를 소켓 활성화(socket activation) 라고 한다. systemd가 먼저 포트를 열어 두고, 연결이 들어오면 그때 서비스를 실행하거나 소켓을 넘겨준다.

이번 글에서는 연결이 올 때마다 짧은 응답 서비스를 실행하는 socket unit을 직접 만들어 동작을 확인하고, Ubuntu ssh.socket의 실제 구성을 읽는다. 관제에서 "포트의 주인이 systemd로 보이는" 상황을 해석하는 기준도 정리한다.


2. 핵심 개념

2-1. socket unit 주요 설정 ([Socket])

설정의미
ListenStream=TCP(또는 UNIX 스트림) 주소·포트
ListenDatagram=UDP
ListenFIFO=이름 있는 파이프
Accept=yes: 연결마다 서비스 인스턴스 생성 / no(기본): 소켓을 서비스 하나에 전달
Service=활성화할 서비스 (기본: 같은 이름)
BindIPv6Only=, FreeBind=바인딩 세부 옵션
MaxConnections=동시 연결 제한 (Accept=yes)

2-2. Accept=yes 와 no

구분Accept=yesAccept=no
서비스 unit템플릿 NAME@.service일반 NAME.service
프로세스연결마다 하나 (inetd 방식)서비스 하나가 모든 연결 처리
서비스가 받는 것연결된 소켓 (stdin/stdout)listen 소켓
예간단한 내부 도구sshd, journald, dbus

2-3. 관련 명령

명령용도
systemctl list-sockets소켓 unit과 포트, 활성화 대상
systemctl status X.socket상태, Listen 주소, Triggers
systemctl show X.socket -p NAccepted,NConnections누적/현재 연결 수
systemctl show X.service -p TriggeredBy어떤 소켓이 이 서비스를 띄우는가

3. 동작 원리

소켓 활성화 — systemd 가 먼저 포트를 열고, 연결이 오면 서비스를 띄운다

systemctl enable --now lab-echo.socket
  → PID 1 이 127.0.0.1:9999 에 bind · listen (서비스 프로세스는 아직 없음)

클라이언트 연결
  → PID 1 이 accept → lab-echo@<번호>-<로컬>-<원격>.service 인스턴스 생성
  → 연결 fd 를 표준입력·출력으로 연결해 ExecStart 실행
  → 한 줄 읽고 응답 → 종료 → 인스턴스 정리

서비스 입장에서는 표준입력으로 읽고 표준출력으로 쓰기만 하면 된다. 네트워크 코드가 없어도 네트워크 서비스가 되는 구조다.


4. 명령어 실습

# 1) (Rocky) socket unit + 템플릿 서비스
printf '[Unit]\nDescription=Lab echo socket\n[Socket]\nListenStream=127.0.0.1:9999\nAccept=yes\n[Install]\nWantedBy=sockets.target\n' \
  > /etc/systemd/system/lab-echo.socket
printf '[Unit]\nDescription=Lab echo per-connection service\n[Service]\nExecStart=/bin/bash -c "read line; echo \\"hello $$line from PID $$$$ at $$(date +%%%%T)\\""\nStandardInput=socket\n' \
  > /etc/systemd/system/lab-echo@.service
systemctl daemon-reload; systemctl enable --now lab-echo.socket
ss -tlnp 'sport = :9999'                               # 포트 주인 확인
systemctl list-units 'lab-echo*' --no-pager --no-legend
echo analyst | nc 127.0.0.1 9999; echo soc | nc 127.0.0.1 9999
systemctl show lab-echo.socket -p NAccepted,NConnections
journalctl -u 'lab-echo@*' --no-pager -o cat | grep -E 'Started|Deactivated' | tail -4

# 2) (Ubuntu) 실제 사례 ssh.socket
systemctl list-sockets --no-pager
systemctl status ssh.socket --no-pager | sed -n '1,6p'
systemctl show ssh.service -p TriggeredBy,ActiveState
ss -tlnp 'sport = :22'

unit 파일 안에서 $는 systemd의 환경변수 치환, %는 지정자다. 셸에 그대로 넘기려면 $$, %%로 쓴다(35편과 같은 규칙). printf 단계의 이스케이프까지 겹쳐 $$$$, %%%%가 되었다.


5. 실행 결과

실제 실행 결과 — Rocky Linux 9.8 · root@rocky9-lab — socket unit 만들기 (연결마다 서비스 실행)

실제 실행 결과 — Ubuntu 24.04.5 · root@ubuntu-lab — 실제 사례: Ubuntu 의 ssh.socket

텍스트 원본(실제 출력):

[root@rocky9-lab ~]# printf '[Unit]\nDescription=Lab echo socket\n[Socket]\nListenStream=127.0.0.1:9999\nAccept=yes\n[Install]\nWantedBy=sockets.target\n' > /etc/systemd/system/lab-echo.socket
[root@rocky9-lab ~]# printf '[Unit]\nDescription=Lab echo per-connection service\n[Service]\nExecStart=/bin/bash -c "read line; echo \\"hello $$line from PID $$$$ at $$(date +%%%%T)\\""\nStandardInput=socket\n' > /etc/systemd/system/lab-echo@.service
[root@rocky9-lab ~]# systemctl daemon-reload; systemctl enable --now lab-echo.socket
Created symlink /etc/systemd/system/sockets.target.wants/lab-echo.socket → /etc/systemd/system/lab-echo.socket.
[root@rocky9-lab ~]# ss -tlnp 'sport = :9999'
State    Recv-Q   Send-Q      Local Address:Port       Peer Address:Port   Process
LISTEN   0        4096            127.0.0.1:9999            0.0.0.0:*       users:(("systemd",pid=1,fd=38))
[root@rocky9-lab ~]# systemctl list-units 'lab-echo*' --no-pager --no-legend
  lab-echo.socket loaded active listening Lab echo socket
[root@rocky9-lab ~]# echo analyst | nc 127.0.0.1 9999; echo soc | nc 127.0.0.1 9999
hello analyst from PID 5891 at 12:32:08
hello soc from PID 5917 at 12:32:08
[root@rocky9-lab ~]# systemctl show lab-echo.socket -p NAccepted,NConnections
NConnections=0
NAccepted=2
[root@rocky9-lab ~]# journalctl -u 'lab-echo@*' --no-pager -o cat | grep -E 'Started|Deactivated' | tail -4
Started Lab echo per-connection service (127.0.0.1:49452).
lab-echo@0-127.0.0.1:9999-127.0.0.1:49452.service: Deactivated successfully.
Started Lab echo per-connection service (127.0.0.1:49468).
lab-echo@1-127.0.0.1:9999-127.0.0.1:49468.service: Deactivated successfully.
root@ubuntu-lab:~# systemctl list-sockets --no-pager
LISTEN                         UNIT                            ACTIVATES
0.0.0.0:22                     ssh.socket                      ssh.service
/run/dbus/system_bus_socket    dbus.socket                     dbus.service
/run/initctl                   systemd-initctl.socket          systemd-initctl.service
/run/systemd/io.systemd.sysext systemd-sysext.socket           -
/run/systemd/journal/dev-log   systemd-journald-dev-log.socket systemd-journald.service
/run/systemd/journal/socket    systemd-journald.socket         systemd-journald.service
/run/systemd/journal/stdout    systemd-journald.socket         systemd-journald.service

7 sockets listed.
Pass --all to see loaded but inactive sockets, too.
root@ubuntu-lab:~# systemctl status ssh.socket --no-pager | sed -n '1,6p'
● ssh.socket - OpenBSD Secure Shell server socket
     Loaded: loaded (/usr/lib/systemd/system/ssh.socket; enabled; preset: enabled)
     Active: active (running) since Thu 2026-09-24 11:48:28 UTC; 43min ago
   Triggers: ● ssh.service
     Listen: 0.0.0.0:22 (Stream)
      Tasks: 0 (limit: 9617)
root@ubuntu-lab:~# systemctl show ssh.service -p TriggeredBy,ActiveState
TriggeredBy=ssh.socket
ActiveState=active
root@ubuntu-lab:~# ss -tlnp 'sport = :22'
State       Recv-Q      Send-Q             Local Address:Port             Peer Address:Port      Process
LISTEN      0           4096                     0.0.0.0:22                    0.0.0.0:*          users:(("sshd",pid=95,fd=3),("systemd",pid=1,fd=75))

6. 결과 해석

관찰의미
LISTEN 127.0.0.1:9999 users:(("systemd",pid=1,fd=38))포트를 연 것은 PID 1 이다. 서비스 프로세스는 아직 없다
lab-echo.socket loaded active listening소켓 unit만 active. 서비스 인스턴스는 목록에 없다
hello analyst from PID 5891, hello soc from PID 5917연결마다 다른 PID 의 프로세스가 응답했다 (Accept=yes)
NAccepted=2, NConnections=0누적 2회 연결을 받았고, 지금 열린 연결은 없다
Started ... (127.0.0.1:49452) → lab-echo@0-127.0.0.1:9999-127.0.0.1:49452.service: Deactivated인스턴스 이름에 접속 순번·로컬·원격 주소 가 들어간다. 로그만으로 누가 접속했는지 알 수 있다
Ubuntu list-sockets: 0.0.0.0:22 ssh.socket → ssh.service22번 포트는 ssh.socket이 열고 ssh.service를 활성화한다
journald·dbus도 소켓 unit시스템 기본 서비스도 소켓 활성화로 병렬 부팅한다
ssh.service TriggeredBy=ssh.socket서비스 쪽에서 활성화 주체를 확인할 수 있다
ss 22번 포트에 sshd(95)와 systemd(1)Accept=no라 listen 소켓을 공유 한다. 16편에서 본 구조의 정체다

7. 보안 관점

주제내용
포트 주인 해석ss -p에 systemd만 보이면 프로세스 목록으로는 원인을 찾을 수 없다. socket unit과 활성화 대상 서비스 를 확인해야 한다
숨은 서비스소켓 활성화 서비스는 평소에는 프로세스가 없어 ps 점검에 드러나지 않는다. 포트 점검(16편)과 list-sockets를 함께 본다
노출 범위ListenStream=9999처럼 주소 없이 쓰면 모든 인터페이스 에 열린다. 내부용은 127.0.0.1:포트로 제한한다
자원 보호Accept=yes 서비스는 연결마다 프로세스를 만든다. MaxConnections=, 서비스 쪽 TasksMax=로 과부하를 막는다
권한 분리낮은 포트는 PID 1이 열고 서비스는 fd만 받으므로, 서비스는 권한 없는 계정으로 돌릴 수 있다 (30편)

8. 보안관제 관점

[Detection]  외부 스캔 결과: 서버에 예상하지 않은 TCP 포트 open
     ↓
[서버 확인]  ss -tlnp 'sport = :<포트>'              → users:(("systemd",pid=1,...))
     ↓
[소켓 unit]  systemctl list-sockets --all | grep <포트>
             systemctl cat <이름>.socket <이름>@.service (또는 .service)
     ↓
[판단]       패키지 소유 여부(rpm -qf / dpkg -S), 생성 시각, 활성화 서비스의 ExecStart
     ↓
[기록]       journalctl -u '<이름>@*' → 접속 순번·원격 주소가 인스턴스 이름에 남음
명령목적
systemctl list-sockets --all모든 소켓 unit과 주소
ss -tlnpe포트 + 소속 cgroup(어느 .socket인지)
systemctl show X.socket -p NAccepted누적 접속 수

9. 실무에서 자주 발생하는 실수

실수결과예방
서비스만 stop소켓이 남아 있어 다음 연결에 다시 시작socket unit도 stop/disable
Accept=yes인데 일반 서비스 이름 사용인스턴스 생성 실패NAME@.service 템플릿
ssh.socket의 포트를 sshd_config만 바꿈포트가 안 바뀜 (Ubuntu 24.04)ssh.socket의 ListenStream도 수정(또는 drop-in)
주소 없이 포트만 지정모든 인터페이스 노출바인딩 주소 명시
ExecStart의 $, % 이스케이프 누락빈 값·지정자로 치환$$, %%

10. 실습 체크리스트

[ ] socket unit 과 템플릿 서비스를 작성했다
[ ] 연결 전에는 포트 주인이 PID 1 이고 서비스 프로세스가 없는 것을 확인했다
[ ] 연결마다 다른 PID 의 인스턴스가 실행되는 것을 확인했다
[ ] NAccepted 와 인스턴스 이름의 원격 주소를 확인했다
[ ] Ubuntu ssh.socket → ssh.service 활성화 구조를 확인했다
[ ] 포트 주인이 systemd 일 때 추적하는 순서를 설명할 수 있다

11. 핵심 정리

  • 소켓 활성화는 systemd가 포트를 먼저 열고, 연결이 오면 서비스를 실행하거나 소켓을 넘기는 구조다.
  • Accept=yes는 연결마다 인스턴스, Accept=no는 서비스 하나가 listen 소켓을 받는다.
  • 포트의 주인이 systemd로 보이면 list-sockets로 socket unit과 활성화 서비스를 찾는다.
  • Ubuntu 24.04의 SSH는 ssh.socket으로 동작해 포트 변경도 socket unit에서 해야 한다.
  • 소켓 활성화 서비스는 평소 프로세스가 없어 ps 점검만으로는 보이지 않는다.

12. 다음 편 예고

다음 글 「38. systemd 서비스 샌드박싱」 에서는 systemd-analyze security로 서비스의 노출 점수 를 측정하고, ProtectSystem, PrivateTmp, NoNewPrivileges 같은 샌드박스 옵션을 drop-in으로 적용해 점수가 어떻게 바뀌는지 확인한다.


참고 자료


시리즈 이동

profile
코드에 숨겨진 위협을 읽고 AI로 보안의 미래를 설계합니다. 프론트엔드 개발 경험을 자산 삼아 더 견고하고 지능적인 보안 운영 시스템을 구축해 나가는 과정을 기록합니다

0개의 댓글