Ubuntu에서 서비스(예: Nginx + php-fpm) 맥락으로 Composer를 돌리다 보면, 같은 Composer라도 “어떤 사용자로 실행되었는지” 때문에 에러 양상이 완전히 달라집니다. 특히 캐시 디렉터리, vendor 쓰기 권한, HOME 환경변수, 네트워크/SSL, 그리고 보안상 권한 제한이 얽히면 재현이 어렵습니다.
아래는 “에러 메시지 그대로” 따라갈 수 있도록 if/then 분기 트리로 정리했습니다.
0) 먼저 확인: Composer를 php-fpm이 직접 실행하고 있나요?
Composer는 원칙적으로 배포/빌드 단계에서 CLI로 실행하는 도구입니다. 그런데 “웹 요청으로 Composer를 실행”하도록 만들어진 관리자 페이지/훅이 있으면, php-fpm 워커 권한(www-data 등)으로 실행되며 보안 리스크가 급증합니다.
- 권장: SSH/CI에서 CLI로 composer install/update 실행
- 부득이하게 웹에서 트리거해야 한다면: 특정 IP만 허용, 강한 인증, 실행 명령 고정(사용자 입력으로 명령 구성 금지), 작업 디렉터리/권한 최소화
이 글의 분기 트리는 “CLI에서 실행했는데도 php-fpm과 동일한 권한 이슈가 나는 경우(예: 배포 스크립트가 www-data로 실행됨)”와 “웹 트리거로 실행되는 경우” 모두를 다룹니다.
1) if 에러에 Permission denied / Operation not permitted가 보이면
대표 메시지
- Failed to open stream: Permission denied
- mkdir(): Permission denied
- Could not create directory ...
- Cannot create cache directory ...
- then 1-1: 어떤 경로에 쓰려다 실패했는지 먼저 잡습니다. 보통은 vendor/, composer.lock, ~/.cache/composer(또는 /var/www/.cache/...), /tmp입니다.
- then 1-2: 실행 사용자를 확인합니다. CLI면 현재 사용자, 웹이면 php-fpm pool user(대개 www-data)입니다.
해결 방향(안전한 우선순위)
- 권장: 프로젝트 소유자를 배포 사용자로 통일하고, 배포 사용자가 composer install을 수행합니다(웹서버 계정이 소스 트리를 쓰지 않게).
- 공유가 필요: 배포 사용자 + 웹서버 그룹을 분리해 그룹 권한으로 조정합니다. 디렉터리는 group write + setgid(그룹 상속)만 최소 적용합니다.
- 금지에 가깝게: vendor나 프로젝트 전체를 777로 두는 방식은 피합니다.
보안 체크(권한)
- 웹서버 계정(www-data)이 composer.json, composer.lock을 수정할 수 있으면 공급망 리스크가 커집니다(웹 취약점 → 의존성 변조로 확장).
- php-fpm pool의 user/group가 너무 강한 권한(예: deploy, root)에 묶여 있지 않은지 확인합니다.
- 웹에서 Composer를 실행한다면, 실행 디렉터리를 프로젝트 루트로 고정하고 사용자 입력으로 경로를 받지 않도록 합니다(경로 조작/디렉터리 트래버설 방지).
2) if “PHP temp directory … is not writable” / “/tmp … Permission denied”가 보이면
대표 메시지
- PHP temp directory (/tmp) is not writable to Composer
- Failed to write file into /tmp
- then 2-1: php.ini의 sys_temp_dir, PHP-FPM pool의 php_admin_value[upload_tmp_dir], OS의 /tmp 마운트 옵션(noexec 등)을 확인합니다.
- then 2-2: 격리 강화를 위해 /tmp가 제한적인 환경이라면, Composer가 사용할 임시 디렉터리를 프로젝트 외부의 안전한 경로로 분리합니다(예: /var/tmp/composer-tmp 같은 전용 디렉터리).
보안 체크(입력/권한)
- 임시 디렉터리를 웹루트 하위로 두지 않습니다(다운로드된 파일이 노출될 수 있음).
- 웹 트리거에서 “임시 경로를 파라미터로 받는 형태”는 금지합니다. 고정값만 쓰세요.
3) if “Cannot create cache directory” / “COMPOSER_HOME” 관련이 보이면
대표 메시지
- Cannot create cache directory /home/xxx/.cache/composer
- The HOME or COMPOSER_HOME environment variable must be set
- Failed to initialize global composer
- then 3-1: php-fpm/비대화형 환경에서는 HOME이 비어 있는 경우가 있습니다. 이때 Composer가 캐시 경로를 못 잡고 실패합니다.
- then 3-2: 실행 환경에서 COMPOSER_HOME, COMPOSER_CACHE_DIR를 “쓰기 가능한 전용 디렉터리”로 명시합니다.
운영 팁
- 캐시 디렉터리는 프로젝트 루트 밖(예: /var/cache/composer)으로 두고, 소유자/권한을 최소화합니다.
- 여러 프로젝트가 같은 캐시를 공유한다면 권한 충돌이 잦습니다. 프로젝트별/사용자별로 분리하면 트러블이 줄어듭니다.
보안 체크(권한)
- COMPOSER_HOME에 auth.json(토큰)이 들어갈 수 있습니다. 웹서버 계정이 읽을 수 있는 위치에 두면 토큰 유출 위험이 커집니다.
- 권한은 가능한 한 700(소유자만) 성격으로 운용하고, 공유가 필요할 때만 최소로 엽니다.
4) if SSL/네트워크: “cURL error 60” / “Failed to enable crypto” / “Could not resolve host”가 보이면
대표 메시지
- cURL error 60: SSL certificate problem: unable to get local issuer certificate
- failed to open stream: operation failed / Failed to enable crypto
- Could not resolve host: repo.packagist.org
- Connection timed out
- then 4-1: 서버의 CA 번들이 최신인지(ubuntu: ca-certificates) 확인합니다. 사내 프록시/SSL inspection 환경이면 체인 신뢰 문제가 흔합니다.
- then 4-2: DNS가 불안정하면 resolve host 에러가 납니다. OS 레벨(systemd-resolved), 컨테이너라면 Docker DNS 설정도 같이 봅니다.
- then 4-3: php-fpm과 CLI의 php.ini가 다를 수 있습니다. CLI에서는 되고, 웹 트리거에서는 안 되면 php -i와 phpinfo()의 차이를 비교해야 합니다(특히 openssl.cafile, curl.cainfo).
보안 체크(입력 검증)
- 네트워크 문제를 이유로 Composer 설정에서 TLS 검증을 끄는 선택은 피하세요. (일시적으로 되더라도 공급망 공격에 취약해집니다.)
- 웹에서 “repository URL을 입력받아 composer config repositories…” 같은 기능을 만들었다면, 허용 리스트로 제한하지 않는 한 SSRF/내부망 접근 위험이 생깁니다.
5) if 메모리/시간: “Allowed memory size exhausted” / “Maximum execution time”가 보이면
대표 메시지
- Allowed memory size of ... bytes exhausted
- Maximum execution time of ... seconds exceeded
- then 5-1: 웹 트리거로 Composer를 실행 중이라면, php-fpm의 memory_limit/max_execution_time 영향을 그대로 받습니다. 큰 프로젝트는 쉽게 터집니다.
- then 5-2: 가능하면 웹 요청에서 Composer 실행을 없애고, 백그라운드 작업(큐/크론/배포 단계)으로 분리합니다.
- then 5-3: 꼭 필요하면 CLI 전용 설정(예: php -d memory_limit=...)로 실행하되, 시스템 전체 기본값을 무턱대고 키우기보다 작업 범위를 분리합니다.
보안 체크(권한)
- 웹 요청으로 긴 작업을 허용하면 DoS에 취약해집니다. 인증/레이트리밋/작업 큐로 보호하세요.
6) 마지막 점검 체크리스트(빠르게 재현/격리하기)
- 실행 주체: 지금 Composer가 누구 권한으로 실행되는지(배포 사용자 vs www-data)
- 쓰기 경로: vendor/, composer.lock, ~/.cache/composer(또는 COMPOSER_CACHE_DIR), /tmp
- 환경변수: HOME, COMPOSER_HOME, COMPOSER_CACHE_DIR가 비어 있지 않은지
- php.ini 차이: CLI PHP와 php-fpm PHP의 설정/확장(openssl, curl) 차이
- 네트워크 신뢰: CA 번들/프록시 환경에서 TLS 검증을 끄지 않았는지
- 보안: 웹에서 Composer 실행/설정 변경 기능이 있다면 입력값(경로/URL/패키지명)을 허용 리스트로 제한했는지
마무리
Composer 문제는 “패키지 자체”보다도, php-fpm이 물고 있는 권한/환경변수/네트워크 신뢰에서 시작하는 경우가 많습니다. 에러 메시지에서 먼저 쓰기 경로와 실행 사용자를 찾아내면, 대부분은 빠르게 좁혀집니다.
특히 웹에서 Composer를 실행하도록 해둔 구조라면, 해결보다 먼저 권한 최소화와 입력 검증(허용 리스트)부터 점검하는 게 안전합니다.