systemd 서비스 보안 강화 완벽 가이드 2026: Sandboxing 디렉티브 실전 활용

systemd-analyze security로 노출 점수를 측정하고, 7가지 핵심 샌드박싱 디렉티브를 단계별로 적용하는 실전 하드닝 가이드. nginx 완전 예시와 디버깅 팁까지 한 번에 정리했습니다.

systemd 서비스 하드닝 완전 가이드 2026

솔직히 말해서, 리눅스 서버를 운영하다 보면 의외로 가장 자주 노출되는 공격 표면은 늘 돌아가고 있는 systemd 유닛들입니다. nginx, postgresql, redis 같은 서비스가 한 번 침해되면 공격자는 그 프로세스의 권한과 파일 시스템 접근 범위를 그대로 물려받게 되죠. 2026년 현재 systemd 257 버전은 ProtectHostname, ProtectClock, RestrictNamespaces 등 40개가 넘는 보안 디렉티브를 제공합니다. 그런데 실제 배포 환경에서 이 기능을 제대로 활용하는 곳은 10%도 채 안 됩니다(개인적으로 컨설팅을 다니면서 가장 많이 마주친 빈틈이기도 합니다).

이 가이드는 systemd-analyze security 명령으로 현재 서비스의 노출 점수를 측정하고, 단계별로 샌드박싱 디렉티브를 적용해서 점수를 1점대까지 끌어내리는 실전 절차를 다룹니다. 모든 예제는 systemd 254 이상에서 검증됐습니다.

왜 systemd 서비스 하드닝이 중요한가

2025년에 발생한 주요 리눅스 사고들을 들여다보면 공통점이 보입니다. 초기 침투는 대부분 애플리케이션 취약점(RCE, SSRF 같은)이었지만, 피해가 그렇게까지 커진 진짜 원인은 따로 있었어요. 바로 해당 서비스가 루트 또는 광범위한 capability를 들고 실행되고 있었다는 점입니다.

systemd의 샌드박싱 디렉티브는 커널 네임스페이스, seccomp, capability bounding set을 한 줄짜리 설정으로 적용할 수 있게 해줍니다. 사실상 가장 비용 대비 효과가 좋은 방어 수단이에요.

이 글의 접근 방식은 [[2026-nyeon-rinogseu-keonel-cve-pokjeung-sidae-reontaim-boho-simcheung-bangeo-jeonryak|커널 CVE 심층 방어 전략]]에서 다룬 심층 방어(defense in depth) 원칙을 사용자 공간(userspace)으로 확장한 것이라고 보면 됩니다.

현재 시스템의 보안 점수 측정하기

systemd는 각 서비스의 노출 정도를 0(완전 격리)부터 10(노출 최대)까지 점수로 보여줍니다. 자, 일단 전체 시스템 현황부터 확인해 봅시다.

sudo systemd-analyze security --no-pager

출력 예시는 이런 식입니다.

UNIT                         EXPOSURE PREDICATE HAPPY
nginx.service                     6.5 MEDIUM
postgresql.service                9.6 UNSAFE
redis-server.service              4.6 OK
ssh.service                       9.6 UNSAFE

점수가 5.0 이상이면 우선 하드닝 대상입니다. 개별 서비스의 세부 분석은 다음 명령으로 들여다볼 수 있어요.

sudo systemd-analyze security nginx.service

이 명령은 각 디렉티브의 현재 값과 권장값, 그리고 그 설정이 노출 점수에 얼마나 기여하고 있는지 가중치를 함께 보여줍니다. 팁을 드리자면, 가중치가 0.2 이상이면서 비활성 상태인 항목부터 손대는 게 가장 효율적입니다.

드롭인(Drop-in) 방식으로 안전하게 적용하기

한 가지 자주 하는 실수가 있습니다. 패키지 매니저가 제공하는 원본 유닛 파일을 직접 수정하면, 패키지 업데이트가 한 번 돌고 나면 변경 사항이 흔적도 없이 사라져요. 반드시 드롭인 디렉토리를 쓰세요.

sudo systemctl edit nginx.service

이 명령은 /etc/systemd/system/nginx.service.d/override.conf 파일을 자동으로 만들어주고 편집기까지 열어줍니다. 원본 유닛은 그대로 두고 오버라이드 값만 차곡차곡 쌓이는 구조라, 롤백도 정말 손쉽습니다.

핵심 샌드박싱 디렉티브 7가지

1. NoNewPrivileges — setuid 차단

이 디렉티브는 execve 호출 시 새로운 권한을 획득하지 못하게 막아줍니다. setuid 바이너리나 file capability를 통한 권한 상승을 원천 차단하는 셈이죠.

[Service]
NoNewPrivileges=true

대부분의 데몬은 이미 필요한 권한을 다 갖고 시작하기 때문에 부작용이 거의 없습니다. 가장 먼저 적용해야 할 디렉티브예요. 망설일 이유가 없습니다.

2. ProtectSystem과 ProtectHome

파일 시스템 쓰기 범위를 제한하는 옵션입니다. strict 모드는 /etc를 포함해서 거의 모든 영역을 읽기 전용으로 만들어버립니다.

[Service]
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/log/nginx /var/lib/nginx /run

ReadWritePaths에 명시한 경로만 쓰기가 허용되니까, 서비스가 쓰기를 시도하는 모든 디렉토리를 사전에 파악해 둬야 합니다. 잘 모르겠다면 strace -e trace=openat,write로 실제 쓰기 경로를 추적해 볼 수 있어요.

3. PrivateTmp과 PrivateDevices

각각 /tmp 네임스페이스와 /dev 노드를 격리해 줍니다.

[Service]
PrivateTmp=true
PrivateDevices=true

PrivateTmp는 서비스 전용 /tmp 마운트를 새로 만들어서, 다른 프로세스의 임시 파일 leak 공격(예: race condition을 이용한 심볼릭 링크 공격)을 막아줍니다. PrivateDevices/dev/null, /dev/zero, /dev/random 같은 안전한 노드를 제외한 나머지 디바이스를 모두 숨겨버립니다.

4. CapabilityBoundingSet — 커널 권한 최소화

리눅스 capability를 화이트리스트 방식으로 제한합니다. 예를 들어 웹 서버가 80/443 포트에 바인딩만 하면 된다면, CAP_NET_BIND_SERVICE 하나만 남기는 게 정석입니다.

[Service]
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE

실제로 어떤 capability가 필요한지 모를 때는, strace -e trace=capset,setuid로 추적해 보거나 일단 모든 capability를 허용한 상태에서 auditd 로그를 분석하는 방법이 가장 확실합니다.

5. SystemCallFilter — seccomp BPF 적용

호출 가능한 시스템 콜을 카테고리 단위로 제한합니다. systemd는 미리 정의된 시스템 콜 그룹(@system-service, @network-io 등)을 친절하게 제공하니까, 일일이 정의할 필요는 없습니다.

[Service]
SystemCallFilter=@system-service
SystemCallFilter=~@privileged @resources
SystemCallArchitectures=native

@system-service는 일반 데몬에서 안전하게 쓸 수 있는 기본 집합이에요. 두 번째 줄의 ~ 기호는 @privileged@resources 그룹을 명시적으로 제외하라는 뜻입니다. 이 설정 하나만으로도 컨테이너 탈출 공격에서 자주 악용되는 unshare, keyctl, perf_event_open 같은 호출을 막을 수 있습니다.

6. RestrictAddressFamilies — 네트워크 패밀리 제한

서비스가 쓸 수 있는 소켓 패밀리를 명시하는 옵션입니다. AF_PACKET이나 AF_NETLINK처럼 강력한 패밀리는 대부분의 일반 서비스에 굳이 필요하지 않습니다.

[Service]
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6

이 설정만 잘 걸어둬도 raw socket을 이용한 ARP 스푸핑이나 스니핑 도구의 실행을 막을 수 있습니다.

7. ProtectKernelTunables과 ProtectKernelModules

커널 파라미터와 모듈 로딩을 보호합니다.

[Service]
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectKernelLogs=true
ProtectControlGroups=true
ProtectProc=invisible
ProcSubset=pid

ProtectProc=invisible은 서비스 안에서 다른 사용자의 프로세스를 /proc에서 아예 볼 수 없게 만듭니다. ps 명령을 통한 정보 수집을 차단하는 데 꽤 효과적이에요.

실전: nginx.service 완전 하드닝

자, 이제 모든 걸 합쳐볼 시간입니다. 다음은 노출 점수를 6.5에서 1.6까지 낮추는 nginx 오버라이드 예시입니다.

[Service]
# 권한 제어
NoNewPrivileges=true
CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_CHOWN CAP_SETUID CAP_SETGID
AmbientCapabilities=CAP_NET_BIND_SERVICE

# 파일 시스템 격리
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
PrivateDevices=true
ReadWritePaths=/var/log/nginx /var/lib/nginx /var/cache/nginx /run

# 커널 보호
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectKernelLogs=true
ProtectControlGroups=true
ProtectClock=true
ProtectHostname=true
ProtectProc=invisible
ProcSubset=pid

# 시스템 콜 필터링
SystemCallFilter=@system-service
SystemCallFilter=~@privileged @resources
SystemCallArchitectures=native

# 네트워크 제한
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
RestrictNamespaces=true
LockPersonality=true
MemoryDenyWriteExecute=true
RestrictRealtime=true
RestrictSUIDSGID=true

# IP 필터링(선택)
IPAddressDeny=any
IPAddressAllow=localhost 10.0.0.0/8

적용한 뒤에는 점수를 다시 측정해 봅니다.

sudo systemctl daemon-reload
sudo systemctl restart nginx.service
sudo systemd-analyze security nginx.service

흔한 함정과 디버깅

1. ReadWritePaths 누락. 솔직히 가장 흔한 사고가 이겁니다. 서비스가 시작하자마자 죽는다면 십중팔구 이것 때문이에요. journalctl -u 서비스명에서 Permission deniedRead-only file system 메시지가 보이면 해당 경로를 추가해 주세요.

2. MemoryDenyWriteExecute는 JIT 컴파일러(Node.js, JVM 같은)를 쓰는 서비스에서는 작동하지 않습니다. 적용 전에 반드시 검증하세요. 안 그러면 새벽에 호출받을 일이 생깁니다.

3. SystemCallFilter로 차단된 호출은 기본적으로 EPERM이 아닌 SIGSYS로 프로세스를 죽여버립니다. 디버깅할 때는 다음처럼 잠시 동작을 바꿔주세요.

SystemCallErrorNumber=EPERM

이 설정은 운영 환경에서는 권장되지 않지만, 어떤 시스템 콜이 차단되고 있는지 식별하기에는 정말 좋습니다. auditctl과 함께 쓰면 더 정확한 그림을 볼 수 있어요.

auditd로 차단된 호출 추적하기

운영 중인 서비스가 어떤 호출 때문에 SIGSYS로 죽는지 알고 싶다면, 다음 audit 규칙을 추가합니다.

sudo auditctl -a always,exit -F arch=b64 -S all -F key=seccomp_audit
sudo ausearch -k seccomp_audit -ts recent | grep SYSCALL

침입 탐지와 연계하고 싶다면 [[ebpf-giban-rinugseu-reontaim-boan-wanbyeog-gaideu-falco-tetragon-keoneol-sujun-wihyeob-tamji|eBPF Falco/Tetragon 가이드]]에서 다룬 런타임 탐지 도구와 함께 묶어서 쓰면 효과가 훨씬 강력해집니다.

전체 시스템을 한 번에 점검하는 스크립트

다음 스크립트는 노출 점수가 5.0 이상인 서비스를 한꺼번에 뽑아줍니다.

#!/usr/bin/env bash
systemd-analyze security --no-pager 2>/dev/null | \
  awk 'NR>1 && $2+0 >= 5.0 {printf "%-40s %s\n", $1, $2}' | \
  sort -k2 -rn

이걸 cron이나 systemd 타이머에 걸어두면 주간 보안 보고서 용도로 아주 쏠쏠합니다(개인적으로 매주 월요일 아침에 슬랙으로 받아 보고 있어요).

FAQ

systemd 보안 디렉티브를 적용하면 서비스 성능에 영향이 있나요?

seccomp BPF와 네임스페이스 격리에 약간의 오버헤드가 있긴 하지만, 일반적인 웹/DB 서비스에서는 측정 가능한 수준이 아닙니다(0.1~0.5% 이하 정도). 다만 IPAddressDeny/Allow는 모든 패킷에 BPF 필터를 적용하기 때문에, 고부하 네트워크 서비스라면 nftables를 별도로 쓰는 쪽이 훨씬 효율적입니다.

SELinux나 AppArmor가 이미 켜져 있어도 systemd 하드닝이 필요한가요?

네, 필요합니다. SELinux나 AppArmor는 MAC(강제적 접근 제어) 계층이고, systemd 샌드박싱은 네임스페이스와 seccomp 기반의 완전히 별개의 계층이거든요. 둘은 서로 다른 공격 벡터를 막기 때문에, 함께 사용했을 때 효과가 가장 큽니다. 특히 SELinux 정책이 아예 작성되지 않은 사내 애플리케이션이라면, systemd 하드닝이 훨씬 빠르고 실용적인 선택이에요.

도커나 podman 컨테이너 안의 프로세스에도 적용되나요?

컨테이너 안의 PID 1이 systemd가 아닌 한, systemd 디렉티브는 호스트의 컨테이너 런타임 서비스(containerd.service, docker.service)에만 적용됩니다. 컨테이너 내부 격리는 컨테이너 런타임 쪽의 --security-opt seccomp, --cap-drop, 그리고 Kubernetes의 securityContext로 제어해야 합니다.

systemd-analyze 점수 0점을 목표로 해야 하나요?

이상적이긴 하지만 현실적이지는 않습니다. 0점에 가까워질수록 서비스가 정상 동작하지 않을 가능성이 급격히 올라가거든요. 경험상 대부분의 인프라 서비스에서는 2.0~3.0 사이가 보안과 가용성의 균형점이라고 봐요. 사실 점수 그 자체보다 중요한 건, 적용한 디렉티브가 실제 위협 모델과 들어맞느냐 하는 부분입니다.

적용 후 서비스가 부팅되지 않을 때 어떻게 복구하나요?

systemctl revert 서비스명 한 줄이면 모든 드롭인 오버라이드를 즉시 제거할 수 있습니다. 부팅 자체가 막힌 경우에는 GRUB 메뉴에서 systemd.unit=rescue.target으로 진입한 다음 /etc/systemd/system/서비스명.service.d/ 디렉토리를 통째로 지워주면 됩니다.

마무리

systemd 샌드박싱은 한 번 잘 설정해 두면 서비스의 라이프타임 내내 보호 효과를 발휘하는, 비용 대비 효과가 가장 큰 리눅스 보안 조치 중 하나입니다. 솔직히 이만큼 가성비 좋은 방어 수단도 드물어요.

오늘 당장 systemd-analyze security를 실행해서 점수가 가장 높은 서비스 세 개부터 하드닝을 시작해 보세요. 한 달이면 시스템 전체의 평균 노출 점수를 절반 이하로 떨어뜨리는 게 충분히 가능합니다.

저자 소개 Editorial Team

Our team of expert writers and editors.