Cloudflare를 통해 웹사이트를 운영할 때 Laravel의 세션, 쿠키, 인증 구조는 기본 상태만으로는 예기치 않은 문제가 발생하기 쉽다. 특히 프록시 환경의 특성 때문에 HTTPS 인식 오류, 세션 유지 실패, CSRF 토큰 검증 오류와 같은 문제가 자주 발생한다.
이 글에서는 다음 내용을 중심으로 Cloudflare 프록시 환경에서 Laravel을 안정적으로 운영하기 위한 설정과 개념을 정리한다.
- Cloudflare 도메인 세팅 시 Laravel 세팅하기
- SESSION_DRIVER 에 대해서
- Redis 설치 방법
- Synology
- macOS
페이지 만료됨 / 세션이 만료되었거나 보안 토큰이 유효하지 않습니다.에 대한 원인과 해결책- Login 페이지에서 기존 세션 무시하기
- 전통적인 LoginController 사용 시
- Laravel Fortify 사용 시
- .env.example 예제
1. Cloudflare 도메인 세팅 시 Laravel 세팅하기
Cloudflare의 프록시는 기본적으로 Reverse Proxy 로 동작한다. 즉, 클라이언트는 Cloudflare에 접속하고, Cloudflare가 Laravel 서버로 요청을 전달하는 구조이다. 이때 Laravel이 실제 요청 정보를 올바르게 인식하도록 다음 설정이 필수적이다.
1-1. TrustProxies 설정
프록시를 신뢰하지 않으면 Laravel은 HTTPS 요청을 HTTP로 오인하거나, 실제 접속 IP를 Cloudflare의 IP로 잘못 인식할 수 있다. 이를 방지하기 위해 TrustProxies 미들웨어를 다음과 같이 설정한다.
<?php
namespace App\Http\Middleware;
use Illuminate\Http\Request;
use Illuminate\Http\Middleware\TrustProxies as Middleware;
class TrustProxies extends Middleware
{
/**
* The trusted proxies for this application.
*
* @var array<int, string>|string|null
*/
protected $proxies = '*';
/**
* The headers that should be used to detect proxies.
*
* @var int
*/
protected $headers = Request::HEADER_X_FORWARDED_FOR
| Request::HEADER_X_FORWARDED_HOST
| Request::HEADER_X_FORWARDED_PORT
| Request::HEADER_X_FORWARDED_PROTO
| Request::HEADER_X_FORWARDED_AWS_ELB;
}
이 설정을 적용하면 Cloudflare가 추가하는 X-Forwarded-* 헤더를 Laravel이 신뢰하고, 다음과 같은 값들을 정확하게 인식할 수 있다.
- 실제 클라이언트 IP (
request()->ip()) - 실제 도메인/호스트 (
request()->getHost()) - 실제 프로토콜 (HTTPS 여부,
request()->secure())
1-2. 도메인 및 세션 관련 .env 설정
Cloudflare를 통해 HTTPS로만 서비스하는 환경이라면 .env 에 다음과 같이 설정하는 것이 일반적이다.
APP_URL=https://domain.co.kr
SESSION_DRIVER=database
SESSION_DOMAIN=.domain.co.kr
SESSION_SECURE_COOKIE=true
SESSION_SAME_SITE=lax
SESSION_LIFETIME=120
SESSION_PATH=/
SESSION_ENCRYPT=true
APP_URL- Laravel이 URL 생성 시 기준으로 사용할 도메인이다.
SESSION_DOMAIN=.domain.co.krdomain.co.kr,www.domain.co.kr등 서브도메인 전체에서 세션을 공유한다.
SESSION_SECURE_COOKIE=true- HTTPS 요청에서만 세션 쿠키를 전송한다.
SESSION_SAME_SITE=lax- 일반적인 웹 애플리케이션에 적합한 SameSite 정책이다.
SESSION_LIFETIME=120- 세션 유지 시간(분 단위)으로, 기본 120분(2시간)이다.
1-3. www 도메인 통일
쿠키는 도메인이 미묘하게 달라지면 공유되지 않는다. 예를 들어, 한 사용자는 https://domain.co.kr 로 접속하고, 다른 사용자는 https://www.domain.co.kr 로 접속할 경우 세션 동작이 꼬일 수 있다.
따라서 Cloudflare 또는 웹서버(Nginx, Apache)에서 다음과 같이 통일하는 것이 바람직하다.
- 모든 요청을
https://domain.co.kr로 리디렉션 www.domain.co.kr→ domain.co.kr301 리디렉션
1-4. Sanctum 사용 시 설정
SPA 또는 API 인증에 Laravel Sanctum을 사용하는 경우 다음 환경 변수를 설정한다.
SANCTUM_STATEFUL_DOMAINS=domain.co.kr,www.domain.co.kr
이 설정을 통해 해당 도메인에서 오는 요청은 쿠키 기반 세션 인증으로 처리할 수 있다.
2. SESSION_DRIVER 에 대해서
세션 드라이버는 Laravel이 세션을 어디에 저장할지를 결정한다. 운영 환경에서는 드라이버 선택이 안정성과 확장성에 직접적인 영향을 준다.
2-1. file 드라이버
SESSION_DRIVER=file
Laravel의 기본값이다. 개발 환경에서는 간단하고 충분하지만, 운영 환경에서는 다음과 같은 문제를 유발할 수 있다.
- 파일 잠금으로 인한 동시성 문제
- 파일 시스템 권한 문제
- 여러 서버/컨테이너 환경에서 세션 공유 불가
- NAS 환경에서 I/O 병목 가능성
운영 환경에서는 가급적 file 드라이버를 사용하지 않는 것이 좋다.
2-2. database 드라이버
SESSION_DRIVER=database
세션을 데이터베이스의 sessions 테이블에 저장하는 방식이다.
사전 준비:
php artisan session:table
php artisan migrate
장점
- 여러 PHP-FPM 프로세스, 컨테이너, 서버 간 세션 공유 가능
- file 드라이버보다 안정적
- 별도의 인프라(Redis) 없이도 사용 가능
단점
- 트래픽 증가 시 DB 부하 증가 가능
- Redis보다 읽기/쓰기 속도가 느릴 수 있음
2-3. redis 드라이버
SESSION_DRIVER=redis
Redis 서버를 세션 저장소로 사용하는 방식으로, 고트래픽 환경에서 가장 많이 사용된다.
장점
- 메모리 기반 저장으로 매우 빠르다.
- 세션 공유에 최적이며, 고부하 환경에도 잘 대응한다.
- 캐시, 큐 등과 함께 통합 운영 가능하다.
단점
- Redis 서버 설치 및 운영 필요
운영 환경(특히 다수의 동시 접속, 로그인·결제가 많은 서비스)에서는 Redis 기반 세션 사용을 가장 우선적으로 고려하는 것이 바람직하다.
3. Redis 설치 방법
3-1. Synology NAS에서 Redis 설치
Synology DSM 7 이상에서는 Docker가 Container Manager 라는 이름으로 제공된다. Redis는 Container Manager를 사용하는 것이 가장 안정적이다.
(1) Redis 컨테이너 이미지 다운로드
- DSM에서 Container Manager 실행
- 좌측 메뉴에서 레지스트리(Registry) 선택
- 검색창에
redis입력 - 공식
redis이미지를 선택 후 다운로드- 태그는
latest또는 특정 버전(예:7또는7-alpine)을 선택
- 태그는
(2) Redis 컨테이너 생성
다운로드한 이미지를 기반으로 새 컨테이너를 생성한다.
- 포트 매핑
- 컨테이너 포트: 6379
- 호스트 포트: 6379 (또는 필요한 포트)
- 볼륨 매핑
- 컨테이너
/data→ NAS/docker/redis/data와 같이 매핑하여 데이터 영속성 확보
- 컨테이너
- 환경 변수 (선택)
- 비밀번호 보호를 위해 다음과 같이 실행 명령을 지정할 수 있다.
예시 실행 명령:
redis-server --requirepass your_redis_password
(3) Laravel에서 Redis 연결 설정
SESSION_DRIVER=redis
CACHE_DRIVER=redis
QUEUE_CONNECTION=redis
REDIS_CLIENT=phpredis
REDIS_HOST=NAS_IP_OR_HOSTNAME
REDIS_PASSWORD=your_redis_password
REDIS_PORT=6379
Synology와 Laravel 서버가 동일 장비인 경우 REDIS_HOST=127.0.0.1 로 설정할 수 있다.
3-2. macOS에서 Redis 설치
macOS에서는 Homebrew를 사용하면 손쉽게 Redis를 설치할 수 있다.
(1) 설치
brew install redis
(2) 서비스로 실행
brew services start redis
시스템 부팅 시 자동 시작되도록 설정된다.
(3) 동작 확인
redis-cli ping
PONG 이 응답되면 정상 동작 중이다.
(4) Laravel 설정
Synology와 동일하게 .env 에 Redis 연결 정보를 입력한다.
SESSION_DRIVER=redis
CACHE_DRIVER=redis
QUEUE_CONNECTION=redis
REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
4. “페이지 만료됨 / 세션이 만료되었거나 보안 토큰이 유효하지 않습니다.” 원인과 해결책
이 메시지는 Laravel에서 419 응답을 반환할 때 출력되는 대표적인 문구이다.
주로 다음 두 가지 상황에서 발생한다.
- 세션 만료(Session Timeout)
- CSRF 토큰 불일치(Token Mismatch)
4-1. 대표적인 발생 시나리오
- 사용자가 로그인 후 오랜 시간 아무 동작 없이 페이지를 열어둔 경우
- 세션 만료 시간(예: 120분)이 지난 이후 예전에 열어 둔 폼을 전송
- Cloudflare 및 프록시 설정 문제로 HTTPS/도메인 인식이 꼬인 경우
- 세션 쿠키가 브라우저에서 정상적으로 전송되지 않는 경우
SESSION_DOMAIN,SESSION_SECURE_COOKIE,SESSION_SAME_SITE값과 실제 접속 도메인이 맞지 않는 경우
4-2. 세션 만료 시간 조정
세션 유지 시간이 너무 짧으면 사용자 경험이 나빠질 수 있다. 서비스 특성에 맞게 SESSION_LIFETIME 값을 조정한다.
SESSION_LIFETIME=120 # 2시간
예를 들어 내부 업무 시스템이라면 480(8시간) 정도로 늘리는 것도 가능하다. 다만 세션 유지 시간이 길어질수록 보안 리스크도 증가하므로 서비스 특성에 맞는 적절한 값을 선택해야 한다.
4-3. 세션/쿠키 동작 점검 포인트
브라우저 개발자 도구를 통해 다음을 확인할 수 있다.
- 로그인 페이지에서 세션 쿠키와 CSRF 쿠키가 발급되는지
- 예:
XSRF-TOKEN,laravel_session혹은 커스텀 세션 이름
- 예:
- 쿠키의 속성
- Domain:
.domain.co.kr또는domain.co.kr - Secure: 체크 여부
- SameSite:
Lax또는None
- Domain:
- 로그인 POST 요청 시 Request Headers의
Cookie항목에 해당 쿠키들이 포함되는지
이 과정에서 쿠키가 전혀 전송되지 않거나, 도메인이 달라지는 문제가 있다면 세션/토큰 오류가 발생한다.
4-4. 419 에러 페이지 커스터마이징
기능적으로는 문제가 아니라 보안상 정상적인 동작이더라도, 사용자에게는 혼란스러운 메시지로 느껴질 수 있다. 따라서 419 오류 페이지를 사용자 친화적으로 커스터마이징하는 것이 좋다.
resources/views/errors/419.blade.php 예시:
@extends('layouts.app')
@section('content')
<div class="container py-5">
<h1 class="mb-3">세션이 만료되었습니다.</h1>
<p class="mb-4">
일정 시간 동안 활동이 없어 보안을 위해 자동 로그아웃되었습니다.<br>
다시 로그인하신 후 서비스를 이용해 주시기 바랍니다.
</p>
<a href="{{ route('login') }}" class="btn btn-primary">
로그인 페이지로 이동
</a>
</div>
@endsection
5. .env.example 예제
마지막으로, Cloudflare + HTTPS + Laravel + Fortify/Sanctum 조합을 고려한 .env.example 예시를 정리한다.
APP_NAME="Laravel Cloudflare App"
APP_ENV=production
APP_KEY=
APP_DEBUG=false
APP_URL=https://domain.co.kr
LOG_CHANNEL=stack
LOG_LEVEL=info
# Database
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=laravel
DB_PASSWORD=secret
# 세션
SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_DOMAIN=.domain.co.kr
SESSION_SECURE_COOKIE=true
SESSION_SAME_SITE=lax
SESSION_PATH=/
SESSION_ENCRYPT=true
# Cache / Queue / Redis
CACHE_DRIVER=redis
QUEUE_CONNECTION=redis
REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
# Sanctum
SANCTUM_STATEFUL_DOMAINS=domain.co.kr,www.domain.co.kr
# Mail (예시)
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=587
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS="noreply@domain.co.kr"
MAIL_FROM_NAME="Laravel App"
이 예제를 기반으로 실제 환경에 맞게 DB, Redis, 메일 서버 정보 등을 수정하여 사용할 수 있다.
맺음말
Cloudflare와 같은 프록시를 경유하는 환경에서 Laravel 애플리케이션을 안정적으로 운영하려면 다음과 같은 요소들이 맞물려야 한다.
- 프록시를 인식하는
TrustProxies설정 - HTTPS 및 도메인 정책에 맞는 세션/쿠키 설정
- 적절한 세션 드라이버 선택(database 또는 redis 권장)
- Redis 도입을 통한 성능 및 안정성 향상
- 세션 만료 후 로그인 UX 개선
위 내용을 순차적으로 적용하면 “페이지 만료됨 / 세션이 만료되었거나 보안 토큰이 유효하지 않습니다.”와 같은 문제가 크게 줄어들고, Cloudflare 환경에서도 Laravel 인증과 세션이 안정적으로 동작하는 구조를 만들 수 있다.
'Laravel' 카테고리의 다른 글
| Laravel 12 + Livewire 3 + React(Inertia) 기반 관리자 및 하이브리드 레이아웃 구성 가이드 (0) | 2025.12.22 |
|---|---|
| Laravel에서 회원가입 메일 인증 시 Amazon SES(Amazon Simple Email Service) 설정 방법 (서울 리전 기준) (0) | 2025.11.22 |
| Cafe24에서 라라벨 호스팅 방법, php8.4 + composer 설치 (0) | 2025.08.21 |
| Laravel 전용 MCP 서버 - boost (2) | 2025.08.17 |
| Livewire 와 Alpine.js 개념잡기 (3) | 2025.08.17 |