<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>레거시 PHP 전문가 프리터코더</title>
    <link>https://freetercoder.tistory.com/</link>
    <description>일용직 개발자에서 레거시 PHP 코드 전문가로 진화중.
freetercoder@gmail.com</description>
    <language>ko</language>
    <pubDate>Sat, 24 Oct 2026 02:48:53 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>프리터코더</managingEditor>
    <image>
      <title>레거시 PHP 전문가 프리터코더</title>
      <url>https://tistory1.daumcdn.net/tistory/8362315/attach/e124447c8d6248db81d6530018df275f</url>
      <link>https://freetercoder.tistory.com</link>
    </image>
    <item>
      <title>반복 SQL 자동화 팁</title>
      <link>https://freetercoder.tistory.com/104</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&quot;매일 아침 어제 주문 현황 엑셀로 뽑아주세요.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매일 똑같은 작업 반복 중입니다. 작은 자동화로 큰 차이를 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레거시 시스템에는 &quot;자동 리포트&quot; 기능이 없는 경우가 많습니다. 담당자가 매일 phpMyAdmin에 들어가서 쿼리 복사 =&amp;gt; 실행 =&amp;gt; 결과 복사 =&amp;gt; 엑셀에 붙여넣기를 반복합니다. 하루 10분씩 소모, 1년이면 60시간입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;간단 PHP 스크립트로 자동화&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예제 1: 매일 주문 현황 CSV 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;daily_report.php&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;?php
$conn = mysqli_connect('localhost', 'user', 'pass', 'dbname');
mysqli_set_charset($conn, 'utf8mb4');

$yesterday = date('Y-m-d', strtotime('-1 day'));
$query = &quot;SELECT order_id, customer_name, product, price, order_date 
            FROM orders 
            WHERE DATE(order_date) = '$yesterday'&quot;;

$result = mysqli_query($conn, $query);

$filename = &quot;orders_&quot; . $yesterday . &quot;.csv&quot;;
$fp = fopen($filename, 'w');

// CSV 헤더
fputcsv($fp, ['주문번호', '고객명', '상품', '금액', '주문일시']);

while ($row = mysqli_fetch_assoc($result)) {
    fputcsv($fp, $row);
}

fclose($fp);
echo &quot;CSV 생성 완료: $filename\n&quot;;
?&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;cron으로 매일 실행&lt;/h3&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;0 9 * * * /usr/bin/php /path/to/daily_report.php&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매일 오전 9시에 자동 실행되며 CSV 파일 생성&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;예제 2: 이메일로 자동 발송&lt;/h2&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;?php
// 위 스크립트에 이메일 발송 추가
mail('manager@company.com', 
        '어제 주문 현황', 
        '첨부 파일 확인하세요.',
        'From: system@company.com',
        ['-f첨부파일' =&amp;gt; $filename]);
?&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;예제 3: 엑셀 파일 생성 (PHPExcel)&lt;/h2&gt;
&lt;pre class=&quot;php&quot;&gt;&lt;code&gt;require 'PHPExcel.php';
$excel = new PHPExcel();
$sheet = $excel-&amp;gt;getActiveSheet();

$row = 1;
$sheet-&amp;gt;setCellValue('A'.$row, '주문번호');
$sheet-&amp;gt;setCellValue('B'.$row, '고객명');

while ($data = mysqli_fetch_assoc($result)) {
    $row++;
    $sheet-&amp;gt;setCellValue('A'.$row, $data['order_id']);
    $sheet-&amp;gt;setCellValue('B'.$row, $data['customer_name']);
}

$writer = PHPExcel_IOFactory::createWriter($excel, 'Excel2007');
$writer-&amp;gt;save('report.xlsx');&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Windows 작업 스케줄러 (cron 없을 때)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제어판 =&amp;gt; 작업 스케줄러 =&amp;gt; 작업 만들기:&lt;/p&gt;
&lt;pre class=&quot;taggerscript&quot;&gt;&lt;code&gt;프로그램: C:\php\php.exe
인수: C:\www\daily_report.php
트리거: 매일 오전 9시&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;웹 기반 간단 대시보드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;dashboard.php&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;h1&amp;gt;오늘 통계&amp;lt;/h1&amp;gt;
&amp;lt;?php
$conn = mysqli_connect(...);
$today_orders = mysqli_fetch_assoc(mysqli_query($conn, &quot;SELECT COUNT(*) as cnt FROM orders WHERE DATE(order_date) = CURDATE()&quot;))['cnt'];
$today_revenue = mysqli_fetch_assoc(mysqli_query($conn, &quot;SELECT SUM(price) as sum FROM orders WHERE DATE(order_date) = CURDATE()&quot;))['sum'];
?&amp;gt;
&amp;lt;p&amp;gt;오늘 주문: &amp;lt;?= $today_orders ?&amp;gt;건&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;오늘 매출: &amp;lt;?= number_format($today_revenue) ?&amp;gt;원&amp;lt;/p&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;담당자가 브라우저에서 &lt;code&gt;dashboard.php&lt;/code&gt; 접속하면 바로 확인&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Google Sheets 연동&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Google Sheets API로 자동으로 스프레드시트 업데이트도 가능. 담당자는 구글 시트만 열면 최신 데이터 확인.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실전 팁&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스크립트 실행 결과를 로그로 남기기&lt;/li&gt;
&lt;li&gt;에러 발생 시 알림 (메일, 슬랙)&lt;/li&gt;
&lt;li&gt;민감 정보(DB 비밀번호)는 별도 config 파일로 분리&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작은 자동화로 큰 차이를 만듭니다. 매일 10분 아끼면 1년에 60시간, 한 달 치 인건비입니다. 자동화는 곧 시간 절약입니다.&lt;/p&gt;</description>
      <category>PHP 실무</category>
      <author>프리터코더</author>
      <guid isPermaLink="true">https://freetercoder.tistory.com/104</guid>
      <comments>https://freetercoder.tistory.com/104#entry104comment</comments>
      <pubDate>Fri, 23 Oct 2026 14:59:19 +0900</pubDate>
    </item>
    <item>
      <title>기술 전문가는 &amp;lsquo;고치지 않고도 지켜내는 방법&amp;rsquo;을 안다</title>
      <link>https://freetercoder.tistory.com/103</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&quot;이번 달도 문제없이 잘 돌아갔네요.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유지보수 보고서를 받으면 늘 이 문장이 있습니다. 처음엔 &quot;그럼 이번 달엔 아무것도 안 한 건가?&quot; 싶었습니다. 하지만 이게 바로 진짜 전문가의 실력입니다. &lt;b&gt;문제가 안 생기게 만드는 것.&lt;/b&gt;&lt;br /&gt;고장 나서 고치는 건 누구나 합니다. 고장 안 나게 지키는 게 진짜 기술입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예방의 기술이 왜 중요할까요?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;문제가 터지기 전에 막으면 비용이 1/10입니다.&lt;/b&gt; 작은 경고 신호를 미리 잡아서 조치하면 몇만 원이지만, 터지고 나서 복구하면 몇백만 원입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;고객은 아무것도 모르게 지나갑니다.&lt;/b&gt; 장애가 없으니 불만도 없고, 이탈도 없습니다. 조용히, 안정적으로 비즈니스가 돌아갑니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;대표님이 편히 주무십니다.&lt;/b&gt; &quot;혹시 사이트 다운되면 어쩌지?&quot; 걱정 없이 본업에 집중할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 사례를 봅시다.&lt;br /&gt;한 쇼핑몰은 유지보수 계약을 맺고 1년간 큰 사고가 없었습니다. 대표님은 처음엔 &quot;유지보수 비용이 아까운 건 아닐까?&quot; 생각했습니다. &quot;별일 없는데 매달 40만 원씩 나가네...&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 월간 보고서를 자세히 보니 다른 이야기였습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1월: 하드디스크 S.M.A.R.T 경고 감지 &amp;rarr; 사전 교체 (다운타임 제로)&lt;/li&gt;
&lt;li&gt;3월: PHP 보안 취약점 발표 &amp;rarr; 즉시 패치 (해킹 예방)&lt;/li&gt;
&lt;li&gt;5월: 트래픽 급증 예상(세일 기간) &amp;rarr; 서버 리소스 사전 확장 (속도 저하 방지)&lt;/li&gt;
&lt;li&gt;7월: 데이터베이스 쿼리 느려짐 감지 &amp;rarr; 인덱스 최적화 (속도 개선)&lt;/li&gt;
&lt;li&gt;9월: 결제 모듈 만료 예정 &amp;rarr; 사전 갱신 (결제 중단 방지)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;별일 없었던&quot; 게 아니라, &lt;b&gt;전문가가 조용히 5번의 큰 사고를 막은 겁니다.&lt;/b&gt; 고객은 몰랐습니다. 사이트는 항상 잘 돌아갔으니까요. 대표님은 &quot;이제 유지보수가 왜 필요한지 알겠다&quot;며 계약을 연장했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 다른 사례는 제조업체 ERP였습니다.&lt;br /&gt;전문가가 정기 점검에서 &quot;서버 메모리 사용률이 계속 증가하고 있습니다. 이대로 가면 3개월 후 한계 도달 예상&quot;이라고 보고했습니다. 즉시 메모리 누수 원인을 찾아서 코드를 수정했습니다. 3개월 후? 아무 일도 안 일어났습니다. 시스템은 여전히 잘 돌아갔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 방치했다면? 3개월 후 서버 다운, 업무 마비, 긴급 복구, 수백만 원 손실. 하지만 예방 덕분에 그냥 조용히 지나갔습니다. &lt;b&gt;최고의 유지보수는 아무 일도 안 일어나는 겁니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전문가는 어떻게 &quot;고치지 않고 지킬까요?&quot;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;방법 1: 패턴 인식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몇 년간 수많은 시스템을 봐온 경험으로, &quot;이 패턴은 곧 문제 생긴다&quot;는 걸 압니다. 서버 로그에서 미세한 이상 징후를 감지하고, 큰 문제로 커지기 전에 조치합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;방법 2: 사전 테스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 업데이트나 변경 사항을 실서버에 바로 적용하지 않습니다. 테스트 서버에서 충분히 검증한 후, 안전하다고 확신할 때만 적용합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;방법 3: 모니터링&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 상태, 트래픽, 오류율을 실시간으로 감시합니다. &quot;정상 범위&quot;를 벗어나는 순간 알림이 오고, 즉시 점검합니다. 작은 이상도 놓치지 않습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;방법 4: 문서화와 체크리스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;이럴 때는 이렇게 한다&quot;는 매뉴얼과 체크리스트가 있습니다. 실수를 줄이고, 일관된 품질을 유지합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;방법 5: 보수적 접근&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;최신이 최고&quot;가 아닙니다. 검증된 안정적인 기술을 씁니다. 새 기술은 충분히 안정화된 후에 도입합니다. 모험보다 안정을 선택합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비유하자면, 전문가는 &quot;예언자&quot;가 아니라 &quot;경험자&quot;입니다. 미래를 보는 게 아니라, 과거 경험으로 &quot;이 패턴은 위험하다&quot;는 걸 아는 겁니다. 마치 노련한 선장이 &quot;저 구름 모양은 폭풍 전조다&quot; 하고 미리 피하는 것처럼요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 다른 비유는 &quot;소방관 vs 방화 전문가&quot;입니다. 소방관은 불이 나면 끕니다. 훌륭합니다. 하지만 방화 전문가는 불이 안 나게 합니다. 가연성 물질 없애고, 화재 경보기 설치하고, 직원 교육하고. 예방이 최고의 방어입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장애 나기 전에 점검해 조용히 해결하는 게 진짜 실력입니다. 화려하진 않지만, 효과는 확실합니다. 고객은 몰라도, 대표님은 압니다. &quot;우리 시스템 참 안정적이네&quot; 하고 느낍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예방이 최고의 기술입니다. 고치는 것보다 지키는 게 어렵습니다. 하지만 그게 진짜 가치입니다. 유지보수 보고서에 &quot;이번 달도 문제없음&quot;이라고 적혀 있으면, 박수쳐주세요. 그게 바로 전문가가 일한 증거니까요.&lt;/p&gt;</description>
      <category>PHP 유지보수</category>
      <author>프리터코더</author>
      <guid isPermaLink="true">https://freetercoder.tistory.com/103</guid>
      <comments>https://freetercoder.tistory.com/103#entry103comment</comments>
      <pubDate>Tue, 20 Oct 2026 14:57:22 +0900</pubDate>
    </item>
    <item>
      <title>DB 직접 수정 전 필수 백업 팁</title>
      <link>https://freetercoder.tistory.com/102</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;DELETE 쿼리에 WHERE 조건 안 썼는데 엔터 쳐버렸어요&amp;hellip;&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테이블 전체가 사라졌습니다. 회사 업무가 마비됐죠. 작은 수고가 회사를 구합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB 직접 수정은 위험합니다. SQL 한 줄 실수로 수천, 수만 건 데이터가 날아갑니다. phpMyAdmin, MySQL Workbench 같은 도구는 편리하지만, &quot;실행 취소&quot; 버튼이 없습니다. 한 번 날아가면 끝입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;백업 단계별 가이드&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1단계: 테이블 단위 백업&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;특정 테이블만 수정한다면:&lt;/h4&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;-- 백업 테이블 생성
CREATE TABLE users_backup AS SELECT * FROM users;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;복원:&lt;/h4&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;DROP TABLE users;
RENAME TABLE users_backup TO users;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2단계: mysqldump로 전체 백업&lt;/h3&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;mysqldump -u root -p database_name &amp;gt; backup_before_edit.sql&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;특정 테이블만:&lt;/h4&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;mysqldump -u root -p database_name users orders &amp;gt; backup_tables.sql&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3단계: phpMyAdmin 내보내기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;테이블 선택 &amp;rarr; &quot;내보내기&quot; &amp;rarr; SQL 형식 &amp;rarr; 다운로드&lt;/code&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;작업 전 안전 수칙&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;규칙 1: 백업 먼저, 쿼리는 나중&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;급해도 백업부터. 1분이면 끝납니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;규칙 2: SELECT로 먼저 확인&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;-- 삭제 전
SELECT * FROM users WHERE reg_date &amp;lt; '2020-01-01';

-- 확인 후
DELETE FROM users WHERE reg_date &amp;lt; '2020-01-01';&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;규칙 3: LIMIT 걸기&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;-- 위험
DELETE FROM users WHERE status = 'inactive';

-- 안전 (10개씩 확인하며)
DELETE FROM users WHERE status = 'inactive' LIMIT 10;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;규칙 4: 트랜잭션 사용&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;START TRANSACTION;

DELETE FROM users WHERE id = 123;
SELECT * FROM users WHERE id = 123;  -- 확인

-- 잘못됐으면
ROLLBACK;

-- 맞으면
COMMIT;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;규칙 5: UPDATE는 반드시 WHERE 확인&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;-- 치명적 실수 (전체 비밀번호 같은 값으로!)
UPDATE users SET password = '123456';

-- 올바른 쿼리
UPDATE users SET password = '123456' WHERE id = 99;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;백업 파일 관리&lt;/h2&gt;
&lt;pre class=&quot;dns&quot;&gt;&lt;code&gt;/backups/
    ├── daily/
    │   ├── db_20240527.sql
    │   ├── db_20240526.sql
    │   └── db_20240525.sql
    └── before_major_changes/
        ├── before_user_migration_20240520.sql
        └── before_payment_fix_20240515.sql&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최소 1주일치 일일 백업 + 중요 작업 전 백업 별도 보관&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;복원 연습&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백업만 하고 복원 안 해봤다면 무용지물. 최소 분기에 한 번, 백업 파일로 실제 복원해보세요.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;mysql -u root -p database_name &amp;lt; backup.sql&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실전 사례&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 업체는 &lt;code&gt;UPDATE orders SET status = 'cancelled'&lt;/code&gt; 쿼리에 WHERE 조건을 빼먹어, 전체 주문이 취소 상태로 바뀌었습니다.&lt;br /&gt;다행히 5분 전 백업이 있었고, 3분 안에 복원했습니다. 백업 없었다면? 상상도 하기 싫습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;자동 백업 설정&lt;/h3&gt;
&lt;pre class=&quot;mel&quot;&gt;&lt;code&gt;# cron으로 매일 새벽 2시 자동 백업
0 2 * * * mysqldump -u root -p'password' dbname &amp;gt; /backup/db_$(date +\%Y\%m\%d).sql&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작은 수고가 회사를 구합니다. &quot;설마 내가 실수하겠어?&quot;는 위험한 생각입니다. 누구나 실수합니다.&lt;br /&gt;백업만이 답입니다. 유지보수는 백업입니다.&lt;/p&gt;</description>
      <category>PHP 실무</category>
      <author>프리터코더</author>
      <guid isPermaLink="true">https://freetercoder.tistory.com/102</guid>
      <comments>https://freetercoder.tistory.com/102#entry102comment</comments>
      <pubDate>Fri, 16 Oct 2026 14:40:15 +0900</pubDate>
    </item>
    <item>
      <title>유지보수 계약서에 반드시 포함되어야 할 항목 5가지</title>
      <link>https://freetercoder.tistory.com/100</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;유지보수 계약을 맺기로 했습니다.&lt;br /&gt;업체에서 계약서를 보내왔는데, A4 용지 3장짜리입니다. 법률 용어가 가득하고, 뭔가 중요해 보이는데 정확히 뭘 보장하는지 애매합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;그냥 서명하고 시작할까?&quot; 하고 넘어가기 쉽지만, 잠깐만요. 계약서는 나중에 문제 생겼을 때 유일한 보호막입니다. 제대로 확인하고 넘어가야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유지보수 계약서가 중요한 이유는 &quot;책임 범위&quot;를 명확히 하기 때문입니다.&lt;br /&gt;&quot;이건 해주고, 저건 안 해주고&quot;가 분명해야, 나중에 &quot;계약에 없던 일&quot;이라며 추가 비용 청구당하는 일이 없습니다.&lt;br /&gt;또한 &quot;긴급 상황 대응&quot;, &quot;업무 시간&quot;, &quot;비용 정산 방식&quot; 같은 것들이 명확해야 안심하고 맡길 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 회사는 월 30만 원에 유지보수 계약을 맺었는데, 계약서가 너무 간단했습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;월 30만 원에 시스템 유지보수 제공&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;딱 이 한 줄. 6개월 후 서버가 다운됐고, 긴급 연락했습니다.&lt;br /&gt;업체 답변은 이랬습니다.&lt;br /&gt;&quot;주말 긴급 출동은 계약에 없으니 별도 비용입니다. 50만 원입니다.&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화가 났지만, 계약서엔 정말 그런 내용이 없었습니다. 할 수 없이 50만 원 추가 지불. 이후에도 &quot;이건 계약에 없다&quot;, &quot;저건 추가 비용이다&quot; 하며 분쟁이 반복됐습니다.&lt;br /&gt;결국 1년 만에 계약 해지. 대표님은 &quot;처음 계약서를 꼼꼼히 확인했어야 했다&quot;고 후회했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 &lt;b&gt;유지보수 계약서에 반드시 포함되어야 할 항목 5가지&lt;/b&gt;는 무엇일까요?&lt;/p&gt;
&lt;h1&gt;항목 1: 제공 서비스 범위&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 중요합니다. &quot;유지보수&quot;라는 말이 막연하니, 구체적으로 뭘 해주는지 명시해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꼭 포함되어야 할 내용은 이렇습니다. 이런 내용이 포함되어 있는지 체크하세요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정기 점검 (빈도: 월 1회, 분기 1회 등)&lt;/li&gt;
&lt;li&gt;보안 패치 (PHP, MySQL, 플러그인 업데이트)&lt;/li&gt;
&lt;li&gt;백업 관리 (자동 백업 설정, 백업 테스트)&lt;/li&gt;
&lt;li&gt;성능 모니터링 (서버 상태, 트래픽, 오류 로그)&lt;/li&gt;
&lt;li&gt;긴급 장애 대응 (대응 시간: 2시간 이내, 4시간 이내 등)&lt;/li&gt;
&lt;li&gt;소규모 수정 (월 ○시간 이내 무료 수정)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시: &quot;월 1회 서버 점검, 보안 패치 즉시 적용, 하루 1회 자동 백업, 긴급 장애 2시간 이내 대응, 월 2시간 이내 소규모 수정 무료 포함&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 구체적으로 적혀 있어야, &quot;이건 계약에 없다&quot;는 말을 안 듣습니다.&lt;/p&gt;
&lt;h1&gt;항목 2: 긴급 대응 기준&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;긴급 상황&quot;의 정의와 대응 시간을 명확히 해야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;긴급 상황 정의 (서버 다운, 해킹, 데이터 손실 등)&lt;/li&gt;
&lt;li&gt;대응 시간 (연락 후 ○시간 이내 착수)&lt;/li&gt;
&lt;li&gt;대응 방법 (전화, 이메일, 카카오톡 등)&lt;/li&gt;
&lt;li&gt;업무 시간 외 대응 (주말, 야간 포함 여부 및 추가 비용)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시: &quot;서버 다운 시 연락 후 2시간 이내 복구 착수. 평일 09:00~18:00 무료, 주말/야간은 별도 협의&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 적혀 있으면, 주말에 사고 나도 &quot;얼마를 내야 하는지&quot; 예상할 수 있습니다.&lt;/p&gt;
&lt;h1&gt;항목 3: 비용 및 지불 조건&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;월 얼마인지, 언제 내는지, 추가 비용은 어떤 경우 발생하는지 명확히 해야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;월 비용 (VAT 별도/포함 명시)&lt;/li&gt;
&lt;li&gt;지불 방식 (월말 청구, 선불, 후불 등)&lt;/li&gt;
&lt;li&gt;포함 작업 시간 (월 ○시간 무료, 초과 시 시간당 ○만 원)&lt;/li&gt;
&lt;li&gt;추가 비용 발생 케이스 (신규 기능 개발, 대규모 수정 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시: &quot;월 40만 원 (VAT 별도), 매월 말일 청구, 월 3시간 작업 포함, 초과 시 시간당 10만 원&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 적혀 있으면, 예산 계획을 세울 수 있습니다.&lt;/p&gt;
&lt;h1&gt;항목 4: 계약 기간 및 해지 조건&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언제까지인지, 중간에 해지하려면 어떻게 하는지 명시해야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;계약 기간 (1년, 자동 연장 여부)&lt;/li&gt;
&lt;li&gt;해지 통보 기간 (해지 ○개월 전 통보)&lt;/li&gt;
&lt;li&gt;중도 해지 위약금 (있는지 없는지)&lt;/li&gt;
&lt;li&gt;계약 종료 시 인계 사항 (소스코드, 문서, 권한 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시: &quot;1년 계약, 자동 연장. 해지 시 1개월 전 서면 통보. 위약금 없음. 종료 시 소스코드 및 서버 정보 전달&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 적혀 있으면, 나중에 업체 바꾸고 싶을 때 깔끔하게 정리할 수 있습니다.&lt;/p&gt;
&lt;h1&gt;항목 5: 책임 및 면책 조항&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업체가 뭘 책임지고, 뭘 책임 안 지는지 명확히 해야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;업체 책임 범위 (업체 실수로 인한 장애는 무료 복구)&lt;/li&gt;
&lt;li&gt;고객 책임 범위 (고객 요청 작업으로 인한 문제는 고객 책임)&lt;/li&gt;
&lt;li&gt;불가항력 면책 (자연재해, 호스팅사 문제 등)&lt;/li&gt;
&lt;li&gt;데이터 백업 책임 (업체가 백업을 유지하지만, 최종 책임은 고객)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시: &quot;업체 실수로 인한 장애는 무료 복구. 고객 요청 작업 중 발생한 문제는 협의. 외부 호스팅사 문제는 면책. 백업은 업체가 관리하나 최종 책임은 고객&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 적혀 있으면, 문제 생겼을 때 &quot;누구 잘못&quot;인지 따지기 쉽습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계약서가 안정적 협력의 기반입니다.&lt;br /&gt;아무리 좋은 업체라도, 계약서가 엉성하면 나중에 분쟁이 생깁니다. 반대로 계약서가 명확하면, 서로 신뢰하고 오래 협력할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계약서 받으면 이 다섯가지 항목이 있는지 체크하세요. 없거나 애매하면 보완 요청하세요.&lt;br /&gt;&quot;이런 거까지 필요해요?&quot; 하는 업체보다, &quot;좋은 지적입니다. 명확히 하겠습니다&quot; 하는 업체가 진짜 전문가입니다.&lt;/p&gt;</description>
      <category>PHP 유지보수</category>
      <author>프리터코더</author>
      <guid isPermaLink="true">https://freetercoder.tistory.com/100</guid>
      <comments>https://freetercoder.tistory.com/100#entry100comment</comments>
      <pubDate>Tue, 13 Oct 2026 14:38:19 +0900</pubDate>
    </item>
    <item>
      <title>사소한 수정이 시스템 전체에 미친 파장</title>
      <link>https://freetercoder.tistory.com/101</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;버튼 색깔만 좀 바꿔주세요.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5분이면 끝날 일이라 생각했습니다. CSS 파일 열어서 &lt;code&gt;background: red&lt;/code&gt; 하나만 바꿨죠.&lt;br /&gt;그런데 그 버튼이 결제 흐름 전체를 깨뜨렸습니다. 작은 수정도 위험하다는 걸, 그날 뼈저리게 배웠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오래된 쇼핑몰이었습니다. 결제 프로세스가 여러 단계로 나뉘어 있었고, 각 단계마다 &quot;다음&quot; 버튼이 있었죠. 고객이 &quot;빨간색으로 바꿔달라&quot;고 해서, CSS에서 &lt;code&gt;.btn-next&lt;/code&gt; 클래스의 배경색을 빨강으로 바꿨습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 문제는, 같은 &lt;code&gt;.btn-next&lt;/code&gt; 클래스가 관리자 페이지의 &quot;상품 등록&quot; 버튼에도 쓰이고 있었다는 점입니다. 그리고 그 버튼 색깔이 JavaScript 조건문에 걸려 있었습니다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;// 오래된 레거시 코드
if ($('.btn-next').css('background-color') == 'rgb(0, 123, 255)') {
    // 파란 버튼이면 일반 상품 등록
    submitNormal();
} else {
    // 다른 색이면 할인 상품 등록
    submitDiscount();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;믿기 어렵지만, 실제로 이런 코드가 있었습니다. 버튼 색깔로 로직을 구분한 거죠.&lt;br /&gt;버튼을 빨강으로 바꾸는 순간, 모든 상품이 &quot;할인 상품&quot;으로 등록되기 시작했습니다. 재고 관리가 꼬이고, 정산이 틀어지고, 고객 주문까지 영향을 받았습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;발견까지 3일&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 큰 문제는, 이 버그를 3일 동안 몰랐다는 점입니다. 관리자는 &quot;상품은 잘 등록되네?&quot; 하고 지나갔고, 고객은 결제가 잘 돼서 불만이 없었습니다.&lt;br /&gt;하지만 내부적으로는 재고 수량이 마이너스로 떨어지고, 할인가 적용이 중복으로 들어가며 정산 금액이 틀어졌죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회계팀에서 &quot;이번 달 매출이 이상한데요?&quot;라고 연락이 왔고, 그제야 로그를 뒤져서 원인을 찾았습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;원복과 수습&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당장 CSS를 원래대로 되돌렸고, 잘못 등록된 상품 수백 개를 일일이 수정했습니다.&lt;br /&gt;며칠 밤을 새웠습니다. 고객한테는 &quot;죄송합니다, 버튼 색깔 변경은 시스템 영향 때문에 보류됩니다&quot;라고 사과했죠.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;근본 원인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 뜯어보니, 시스템 전체가 이런 식의 &quot;암묵적 의존성&quot;으로 가득했습니다. CSS 클래스명, HTML ID, 심지어 텍스트 내용까지 JavaScript와 PHP가 하드코딩으로 참조하고 있었죠.&lt;/p&gt;
&lt;pre class=&quot;elixir&quot;&gt;&lt;code&gt;if (strpos($html, '빨간 버튼') !== false) {
    // 특별 처리
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 코드가 수십 군데 있었습니다. 10년 동안 여러 개발자가 덧붙이고 땜질하며 만든 결과물이었습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;재발 방지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 다음 원칙을 세웠습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;사소한 수정도 테스트 서버에서 먼저&lt;/b&gt;: 색깔 하나, 텍스트 한 줄도 반드시 테스트 환경에서 확인&lt;/li&gt;
&lt;li&gt;&lt;b&gt;전체 기능 회귀 테스트&lt;/b&gt;: 주요 프로세스(회원가입, 로그인, 주문, 결제)를 매번 점검&lt;/li&gt;
&lt;li&gt;&lt;b&gt;코드 리뷰&lt;/b&gt;: 수정 전후 diff를 반드시 다른 사람이 확인&lt;/li&gt;
&lt;li&gt;&lt;b&gt;하드코딩 제거&lt;/b&gt;: CSS 속성이나 텍스트를 조건문에 쓰지 않기&lt;/li&gt;
&lt;li&gt;&lt;b&gt;변경 이력 기록&lt;/b&gt;: &quot;누가, 언제, 왜 수정했는지&quot; 문서화&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;교훈&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레거시 시스템은 &quot;나비효과의 세계&quot;입니다. 작은 변경 하나가 예상 못 한 곳에서 큰 문제를 일으킵니다. &quot;이 정도야 괜찮겠지&quot;란 생각이 가장 위험합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작은 수정도 위험합니다. 반드시 신중하게, 테스트 환경에서, 충분한 검증 후에 적용하세요. &quot;급하게&quot; 고치면 &quot;두 배로&quot; 시간 잡아먹습니다. 유지보수는 신중함입니다.&lt;/p&gt;</description>
      <category>PHP 실무</category>
      <author>프리터코더</author>
      <guid isPermaLink="true">https://freetercoder.tistory.com/101</guid>
      <comments>https://freetercoder.tistory.com/101#entry101comment</comments>
      <pubDate>Fri, 9 Oct 2026 14:39:27 +0900</pubDate>
    </item>
    <item>
      <title>협력사/고객이 홈페이지를 볼 수 없을 때 생기는 기회비용</title>
      <link>https://freetercoder.tistory.com/99</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;대표님, 오늘 오후 2시에 대형 거래처 담당자가 우리 홈페이지에서 견적서 다운받기로 했는데, 지금 사이트가 안 열려요!&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 보고를 받으면 식은땀이 납니다. 준비한 미팅, 기대하던 계약, 모든 게 사이트 하나 때문에 흔들립니다.&lt;br /&gt;B2B 비즈니스에서 홈페이지 다운타임은 단순한 &quot;불편&quot;이 아니라, 직접적인 &quot;기회 상실&quot;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;협력사/고객이 사이트에 접속 못 할 때 생기는 기회비용은 매출 손실보다 더 클 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;계약 파기 위험. 대형 거래처는 &quot;시스템 안정성&quot;을 중요하게 봅니다. 홈페이지도 제대로 관리 못 하는 회사와 장기 계약을 맺고 싶어 하지 않습니다.&lt;/li&gt;
&lt;li&gt;신뢰 손실입니다. 한 번의 실수가 몇 년간 쌓아온 신뢰를 무너뜨립니다.&lt;/li&gt;
&lt;li&gt;경쟁사 이탈입니다. 우리 사이트가 안 열리면, 고객은 경쟁사로 갑니다. 그리고 다시 돌아오지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 B2B 유통 플랫폼에서 있었던 일입니다.&lt;br /&gt;주요 거래처 담당자가 연간 계약 갱신을 검토하던 중, 플랫폼에 접속해서 거래 내역을 확인하려 했습니다. 그런데 사이트가 다운돼 있었습니다.&lt;br /&gt;서버 점검 중이었는데, 사전 공지가 없었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;담당자는 상사에게 보고했습니다. &quot;이 플랫폼, 중요한 시기에 점검해서 접속이 안 됩니다. 사전 공지도 없었습니다.&quot;&lt;br /&gt;상사는 &quot;그럼 다른 플랫폼 알아보자&quot;고 결정했습니다.&lt;br /&gt;연간 거래액 1억 2,000만 원 규모의 거래처가 &quot;점검 공지 없는 다운타임&quot; 하나 때문에 이탈한 겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플랫폼 측에서 뒤늦게 사과하고 만류했지만, 담당자는 &quot;이미 다른 플랫폼과 계약했습니다&quot;라고 답했습니다.&lt;br /&gt;&lt;b&gt;기회비용은 연간 1억 2,000만 원 &amp;times; 향후 5년 = 6억 원.&lt;/b&gt;&lt;br /&gt;한 번의 실수가 6억 원짜리 고객을 잃게 만든 겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 다른 사례는 제조업체였습니다.&lt;br /&gt;대형 유통사와 납품 계약을 논의 중이었고, 유통사 담당자가 &quot;홈페이지에서 제품 상세 정보 좀 확인하고 연락드리겠습니다&quot;라고 했습니다. 그런데 그날 마침 사이트가 해킹당해서 스팸 광고로 도배됐습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유통사 담당자가 접속해보니, 온갖 불법 광고가 떠 있었습니다. &quot;이 업체, 보안 관리가 안 되는 것 같습니다. 납품 계약 재검토가 필요합니다.&quot;&lt;br /&gt;결국 계약은 무산됐습니다. &lt;b&gt;예상 연간 납품액 3억 원&lt;/b&gt;이 날아간 겁니다. 제조업체 대표님은 &quot;홈페이지 때문에 3억 계약을 놓칠 줄은 몰랐다&quot;며 후회했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 B2B에서 홈페이지가 이렇게 중요할까요?&lt;br /&gt;B2C는 개인 고객이라 한 명 떠나도 다른 고객이 옵니다. 하지만 B2B는 한 거래처가 연간 수천만 원에서 수억 원을 거래합니다. 거래처 하나를 잃는 게 고객 수천 명을 잃는 것과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 B2B 거래처는 &quot;리스크 관리&quot;를 중요하게 봅니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;이 회사와 장기 계약해도 안전한가?&quot;&lt;br /&gt;&quot;시스템이 안정적인가?&quot;&lt;br /&gt;&quot;만약 문제 생기면 우리 비즈니스도 영향받지 않는가?&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 질문에 답해야 합니다. 홈페이지가 자주 다운되거나 보안이 취약하면, &quot;리스크 높은 회사&quot;로 분류되고 거래 대상에서 제외됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 대형 거래처는 납품사 평가 기준이 엄격합니다. 시스템 안정성, 보안 수준, 데이터 관리 체계 등을 점검하고, 기준 미달이면 계약 자체가 안 됩니다.&lt;br /&gt;&quot;홈페이지 관리도 못 하는 회사가 납품도 제대로 할까?&quot; 하는 의심이 드니까요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유지보수 전문가는 B2B 관점에서 시스템을 관리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 다운타임 제로를 목표로 합니다. 특히 업무 시간대(오전 9시~오후 6시)엔 절대 점검하지 않습니다. 점검이 필요하면 새벽이나 주말에 진행하고, 사전 공지를 충분히 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 고가용성 구조를 구축합니다. 메인 서버가 다운돼도 백업 서버가 즉시 투입돼서, 거래처는 아무 문제를 못 느낍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 보안 인증을 취득합니다. ISO 27001 같은 보안 인증이 있으면, 대형 거래처가 신뢰합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 장애 발생 시 즉각 대응합니다. 만약 문제가 생기면 30분 이내 복구하고, 거래처에게 &quot;현재 임시 복구 완료, 재발 방지 조치 진행 중&quot;이라는 공지를 보냅니다.&lt;br /&gt;투명한 커뮤니케이션이 신뢰를 유지합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비용 대비 효과를 계산해봅시다.&lt;br /&gt;월 유지보수 50만 원 &amp;times; 12개월 = 연간 600만 원이 지출됩니다.&lt;br /&gt;이 투자로 보호하는 것은 거래처 계약 (연간 수천만 원~수억 원)입니다.&lt;br /&gt;거래처 하나만 지켜도 본전입니다. 두 개 이상이면 엄청난 이득입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 &quot;예방적 관리&quot;는 거래처에게 좋은 인상을 줍니다.&lt;br /&gt;&quot;이 회사는 시스템을 잘 관리하는구나&quot;, &quot;안정적이구나&quot; 하는 신뢰가 쌓입니다. 계약 갱신할 때도 &quot;계속 여기서 거래하는 게 안전하다&quot;고 판단합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다운타임은 기회 상실입니다. B2C는 고객 한 명, 매출 몇만 원 손실이지만, B2B는 거래처 하나, 매출 수억 원 손실입니다. 규모가 다릅니다.&lt;br /&gt;작은 실수가 큰 손실로 이어집니다. 홈페이지를 지키는 것이 곧 거래처를 지키는 것입니다.&lt;/p&gt;</description>
      <category>PHP 유지보수</category>
      <author>프리터코더</author>
      <guid isPermaLink="true">https://freetercoder.tistory.com/99</guid>
      <comments>https://freetercoder.tistory.com/99#entry99comment</comments>
      <pubDate>Tue, 6 Oct 2026 14:37:24 +0900</pubDate>
    </item>
    <item>
      <title>MySQL &amp;rarr; MariaDB 전환 이슈</title>
      <link>https://freetercoder.tistory.com/98</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;DB 엔진 차이로 서비스가 다운됐습니다. &quot;MySQL이랑 똑같다며?&quot;라고 생각했는데, 미묘한 차이 하나가 전체 쿼리를 멈췄죠. 작은 엔진 차이가 큰 장애를 유발합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MariaDB는 MySQL의 포크(fork) 버전으로, MySQL 창시자가 Oracle 인수 후 만든 오픈소스 DB입니다. 대부분의 경우 MySQL과 호환되지만, 100% 동일하진 않습니다.&lt;br /&gt;특히 특정 함수, 스토리지 엔진, 옵티마이저 동작에서 차이가 있어서, 무심코 전환했다가 쿼리 오류나 성능 저하를 겪는 경우가 많습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 어떤 쇼핑몰은 호스팅 업체가 MySQL 5.5에서 MariaDB 10.x로 일방적으로 전환하면서, &lt;code&gt;GROUP BY&lt;/code&gt; 쿼리가 전부 에러를 일으켰습니다. MySQL 5.5는 느슨했지만, MariaDB는 &lt;code&gt;ONLY_FULL_GROUP_BY&lt;/code&gt; 모드가 기본이라 &lt;code&gt;SELECT&lt;/code&gt; 절에 집계 함수 없는 컬럼이 있으면 에러가 났죠. 주문 내역 조회, 통계 페이지가 전부 먹통이 됐습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;주요 차이점과 대응법&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. ONLY_FULL_GROUP_BY 모드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MariaDB/MySQL 5.7+는 &lt;code&gt;GROUP BY&lt;/code&gt; 시 &lt;code&gt;SELECT&lt;/code&gt; 절의 모든 컬럼이 집계 함수 또는 &lt;code&gt;GROUP BY&lt;/code&gt; 절에 포함돼야 합니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;-- 오류 발생
SELECT id, name, COUNT(*) FROM orders GROUP BY id;

-- 해결 (name도 GROUP BY에 추가)
SELECT id, name, COUNT(*) FROM orders GROUP BY id, name;

-- 또는 집계 함수 사용
SELECT id, MAX(name), COUNT(*) FROM orders GROUP BY id;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;임시 해결&lt;/b&gt; (권장하진 않음)&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;SET GLOBAL sql_mode=(SELECT REPLACE(@@sql_mode,'ONLY_FULL_GROUP_BY',''));&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 날짜/시간 함수 차이&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일부 날짜 함수의 동작이 다릅니다. &lt;code&gt;STR_TO_DATE&lt;/code&gt;, &lt;code&gt;DATE_FORMAT&lt;/code&gt; 등 테스트 필요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 스토리지 엔진&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MariaDB는 &lt;code&gt;Aria&lt;/code&gt;, &lt;code&gt;ColumnStore&lt;/code&gt; 같은 독자 엔진을 제공하지만, MySQL로 다시 돌아갈 때 호환 문제가 생길 수 있습니다. 레거시 코드라면 &lt;code&gt;InnoDB&lt;/code&gt;를 유지하세요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 시퀀스(Sequence) 지원&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MariaDB 10.3+는 &lt;code&gt;CREATE SEQUENCE&lt;/code&gt;를 지원하지만, MySQL은 미지원입니다. 호환성을 유지하려면 &lt;code&gt;AUTO_INCREMENT&lt;/code&gt;를 쓰세요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. JSON 함수&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL 5.7+와 MariaDB 10.2+는 JSON 함수가 다릅니다. 특히 &lt;code&gt;JSON_TABLE&lt;/code&gt;은 MariaDB 10.6+에서 추가됐습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. 성능 차이&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;옵티마이저가 다릅니다. 같은 쿼리라도 실행 계획이 달라질 수 있으니, &lt;code&gt;EXPLAIN&lt;/code&gt;으로 확인하세요.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;전환 전 체크리스트&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;버전 확인&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;SELECT VERSION();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL 5.5 &amp;rarr; MariaDB 10.x 전환 시 점프가 크니 주의.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;2&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;SQL 모드 확인&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;autoit&quot;&gt;&lt;code&gt;SELECT @@sql_mode;&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;3&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;전체 쿼리 테스트&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특히 &lt;code&gt;GROUP BY&lt;/code&gt;, &lt;code&gt;JOIN&lt;/code&gt;, 서브쿼리 중심으로 점검&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;4&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;스토리지 엔진 확인&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;SHOW TABLE STATUS;&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;5&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;백업 필수&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;mysqldump --all-databases &amp;gt; backup_before_switch.sql&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;전환 후 점검&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;에러 로그 (&lt;code&gt;/var/log/mysql/error.log&lt;/code&gt; 또는 MariaDB 로그)&lt;/li&gt;
&lt;li&gt;Slow Query Log (느린 쿼리 찾기)&lt;/li&gt;
&lt;li&gt;주요 기능 수동 테스트 (회원가입, 로그인, 주문, 통계)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;롤백 준비&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 문제가 생기면 즉시 MySQL로 돌아갈 수 있도록, 백업본 유지 및 롤백 절차 문서화.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실무 팁&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;테스트 서버 먼저&lt;/b&gt;: 로컬 또는 스테이징 서버에서 먼저 전환해서 문제를 미리 찾으세요.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;버전 점프 최소화&lt;/b&gt;: MySQL 5.5 &amp;rarr; MariaDB 10.11 같은 큰 점프는 위험. 가능하면 단계적으로.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;호스팅 업체와 협의&lt;/b&gt;: 전환 시기, 롤백 정책 미리 확인.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작은 엔진 차이가 큰 장애를 유발합니다. &quot;거의 같다&quot;는 말에 속지 마세요. 미묘한 차이 하나가 서비스를 멈춥니다. 전환 전 철저한 테스트가 생명입니다. 유지보수는 사전 검증입니다.&lt;/p&gt;</description>
      <category>PHP 실무</category>
      <author>프리터코더</author>
      <guid isPermaLink="true">https://freetercoder.tistory.com/98</guid>
      <comments>https://freetercoder.tistory.com/98#entry98comment</comments>
      <pubDate>Fri, 2 Oct 2026 14:34:15 +0900</pubDate>
    </item>
    <item>
      <title>&amp;quot;에러 한 줄쯤이야&amp;quot;가 매출에 미치는 영향</title>
      <link>https://freetercoder.tistory.com/96</link>
      <description>&lt;p&gt;홈페이지를 보다가 화면 상단에 &lt;code&gt;Warning: Undefined variable in line 234...&lt;/code&gt; 같은 메시지가 떠 있는 걸 본 적 있으신가요?&lt;br&gt;개발자에게 물어보면 &amp;quot;기능엔 문제없습니다. 그냥 경고 메시지예요&amp;quot;라고 답합니다.&lt;br&gt;실제로 사이트는 작동합니다. 그래서 &amp;quot;나중에 고치지 뭐&amp;quot; 하고 넘어갑니다.&lt;br&gt;하지만 고객 눈엔 어떻게 보일까요?&lt;/p&gt;
&lt;p&gt;작은 에러 메시지가 비즈니스에 미치는 영향은 생각보다 큽니다.&lt;br&gt;첫째, 불신을 줍니다. 고객은 기술을 모릅니다. &amp;quot;Warning&amp;quot;이 무슨 뜻인지, 치명적인 건지 아닌지 모릅니다. 그냥 &amp;quot;뭔가 잘못됐구나&amp;quot;, &amp;quot;이 사이트 관리 안 되나?&amp;quot; 하고 느낍니다.&lt;br&gt;둘째, 불안감을 줍니다. 특히 결제 페이지에서 에러 메시지가 뜨면, &amp;quot;여기 카드번호 입력해도 되나?&amp;quot; 하는 의심이 듭니다.&lt;br&gt;셋째, 이탈로 이어집니다. 불신과 불안감이 쌓이면, 고객은 조용히 떠납니다.&lt;/p&gt;
&lt;p&gt;실제로 한 쇼핑몰에서 있었던 일입니다. 랜딩 페이지 상단에 PHP Warning 메시지가 몇 달간 노출됐습니다.&lt;br&gt;내부적으로는 &amp;quot;기능엔 문제없으니 괜찮다&amp;quot;고 판단했습니다. 하지만 광고를 돌려서 신규 고객을 유입시켰을 때, 전환율이 이상하게 낮았습니다. 평소 3%대였는데, 1%대로 떨어진 겁니다.&lt;/p&gt;
&lt;p&gt;A/B 테스트를 해보니 원인이 밝혀졌습니다. 에러 메시지가 있는 페이지는 전환율 1.2%, 에러 메시지를 없앤 페이지는 전환율 3.5%. 같은 상품, 같은 가격인데 에러 메시지 하나 때문에 전환율이 3배 차이 났습니다.&lt;br&gt;월 광고비가 500만 원이었는데, 전환율 차이로 인한 매출 손실은 월 1,000만 원 이상이었습니다. &amp;quot;에러 한 줄쯤이야&amp;quot; 하고 방치한 대가로, 연간 1억 원 넘게 손해 본 겁니다.&lt;/p&gt;
&lt;p&gt;또 다른 사례도 있습니다. B2B 플랫폼이었는데, 로그인 페이지에 &amp;quot;Notice: Undefined index...&amp;quot; 메시지가 떴습니다.&lt;br&gt;기능엔 문제없었지만, 거래처 담당자 한 명이 이걸 보고 상사에게 보고했습니다. &amp;quot;이 플랫폼, 관리가 안 되는 것 같은데 계속 쓸까요?&amp;quot; 상사는 &amp;quot;다른 플랫폼 알아보자&amp;quot;고 결정했고, 결국 그 거래처는 이탈했습니다.&lt;br&gt;연간 거래액 3,000만 원 규모의 고객을 &amp;quot;에러 한 줄&amp;quot; 때문에 잃은 겁니다.&lt;/p&gt;
&lt;p&gt;왜 작은 에러가 이렇게 큰 영향을 미칠까요?&lt;br&gt;고객은 전문가가 아닙니다. &amp;quot;이게 중요한 에러인지, 사소한 경고인지&amp;quot; 구분할 수 없습니다. 그냥 &amp;quot;에러 = 문제&amp;quot; 로 인식합니다. 마치 식당에 들어갔는데 벽에 곰팡이가 핀 걸 보면, &amp;quot;음식은 맛있을 수도 있지만, 위생이 걱정된다&amp;quot;고 느끼는 것과 같습니다.&lt;/p&gt;
&lt;p&gt;특히 첫인상이 중요합니다. 신규 고객이 광고를 보고 처음 사이트에 접속했을 때, 에러 메시지를 보면 &amp;quot;이 사이트 믿을 만한가?&amp;quot; 하는 의심부터 듭니다. 첫인상이 나쁘면, 회복할 기회가 없습니다. 고객은 3초 안에 판단하고, 떠납니다.&lt;/p&gt;
&lt;p&gt;또한 경쟁사와 비교됩니다. A사 홈페이지는 깔끔한데, B사 홈페이지는 에러 메시지가 떠 있으면, 당연히 A사를 선택합니다. 같은 제품, 같은 가격이라도 &amp;quot;신뢰&amp;quot;를 주는 쪽으로 갑니다.&lt;/p&gt;
&lt;p&gt;유지보수 전문가는 이런 &amp;quot;작은 디테일&amp;quot;을 놓치지 않습니다. &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;에러 메시지를 숨깁니다. 개발 환경에서는 에러를 보여주지만, 실제 운영 환경에서는 고객에게 보이지 않게 설정합니다. PHP 설정에서 &lt;code&gt;display_errors = Off&lt;/code&gt;로 바꾸는 것만으로도 해결됩니다.&lt;/li&gt;
&lt;li&gt;에러를 근본적으로 해결합니다. &amp;quot;보이지만 않으면 되지&amp;quot; 가 아니라, 왜 에러가 나는지 찾아서 수정합니다. 변수 선언 누락, 함수 호출 오류 같은 작은 버그들을 하나씩 잡습니다.&lt;/li&gt;
&lt;li&gt;사용자 친화적 에러 페이지를 만듭니다. 만약 진짜 오류가 발생하면, 기술적 메시지가 아니라 &amp;quot;잠시 후 다시 시도해주세요&amp;quot; 같은 친절한 안내를 보여줍니다. 고객이 당황하지 않게요.&lt;/li&gt;
&lt;li&gt;정기적으로 사이트를 점검합니다. &amp;quot;혹시 어딘가에 에러 메시지가 노출되고 있진 않은가?&amp;quot; 확인합니다. 특히 새 기능을 추가하거나 수정한 후엔 꼼꼼히 체크합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;비용은 거의 안 듭니다. 에러 메시지 숨기는 건 설정 한 줄이고, 버그 수정도 대부분 간단합니다. 몇 만 원이면 충분합니다.&lt;br&gt;하지만 그 효과는 엄청납니다. 전환율 개선, 고객 신뢰 향상, 브랜드 이미지 개선. 작은 투자로 큰 효과를 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;작은 에러도 고객 눈에는 불신입니다. &amp;quot;이 정도는 괜찮겠지&amp;quot;가 아니라, &amp;quot;고객은 어떻게 느낄까?&amp;quot;를 생각해야 합니다. 디테일이 신뢰를 만듭니다. 완벽하진 않아도, 최소한 &amp;quot;관리되는 사이트&amp;quot;처럼 보여야 합니다. 에러 한 줄을 고치는 게, 매출 한 줄을 올리는 겁니다.&lt;/p&gt;</description>
      <category>PHP 유지보수</category>
      <author>프리터코더</author>
      <guid isPermaLink="true">https://freetercoder.tistory.com/96</guid>
      <comments>https://freetercoder.tistory.com/96#entry96comment</comments>
      <pubDate>Tue, 29 Sep 2026 14:32:03 +0900</pubDate>
    </item>
    <item>
      <title>.htaccess 접근 제한 팁</title>
      <link>https://freetercoder.tistory.com/97</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;관리자 페이지가 외부에 다 노출된 사례를 봤습니다. &lt;code&gt;/admin&lt;/code&gt; URL만 알면 누구나 접속 가능했죠. 간단한 조치로 큰 보안 효과를 낼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;.htaccess&lt;/code&gt; 파일은 Apache 웹서버의 디렉토리별 설정파일입니다. 작은 텍스트 파일 하나로 URL 리다이렉션, 접근 제어, 캐싱, 에러 페이지 등을 간단히 설정할 수 있죠. 레거시 PHP 사이트 대부분이 이 파일로 보안과 기능을 관리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 이 파일의 존재와 중요성을 모르는 관리자가 많다는 점입니다. 실제로 어떤 쇼핑몰은 &lt;code&gt;/admin&lt;/code&gt; 디렉토리에 아무 제한 없이 관리자 페이지가 열려 있었고, 구글에 &quot;site:example.com admin&quot;으로 검색하면 바로 나왔습니다. 누구나 관리자 로그인 페이지에 접속할 수 있었죠. 다행히 비밀번호가 뚫리진 않았지만, Brute Force 공격 시도가 무수히 많았습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;핵심 활용법&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 특정 디렉토리 IP 제한&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관리자 페이지는 사무실 IP에서만 접속 가능하게 설정하세요.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# /admin/.htaccess
Order Deny,Allow
Deny from all
Allow from 123.123.123.123  # 허용할 IP
Allow from 124.124.0.0/16   # IP 대역도 가능&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Apache 2.4 이상은 문법이 다릅니다:&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Require ip 123.123.123.123
Require ip 124.124.0.0/16&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 특정 파일 접근 차단&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;config.php&lt;/code&gt;, &lt;code&gt;.env&lt;/code&gt; 같은 설정 파일 노출 방지:&lt;/p&gt;
&lt;pre class=&quot;apache&quot;&gt;&lt;code&gt;&amp;lt;FilesMatch &quot;^(config|\.env|\.git)&quot;&amp;gt;
    Order allow,deny
    Deny from all
&amp;lt;/FilesMatch&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 디렉토리 리스팅 차단&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일 목록이 브라우저에 보이지 않도록:&lt;/p&gt;
&lt;pre class=&quot;apache&quot;&gt;&lt;code&gt;Options -Indexes&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 파일 업로드 디렉토리 PHP 실행 금지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업로드 폴더에서 악성 PHP 파일 실행 차단:&lt;/p&gt;
&lt;pre class=&quot;apache&quot;&gt;&lt;code&gt;# /uploads/.htaccess
&amp;lt;FilesMatch &quot;\.php$&quot;&amp;gt;
    Order allow,deny
    Deny from all
&amp;lt;/FilesMatch&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. HTTP &amp;rarr; HTTPS 리다이렉션&lt;/h3&gt;
&lt;pre class=&quot;apache&quot;&gt;&lt;code&gt;RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. www 붙이기/빼기 통일&lt;/h3&gt;
&lt;pre class=&quot;apache&quot;&gt;&lt;code&gt;# www 붙이기
RewriteEngine On
RewriteCond %{HTTP_HOST} !^www\.
RewriteRule ^(.*)$ https://www.%{HTTP_HOST}/$1 [R=301,L]&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7. 특정 User-Agent 차단&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;악성 봇 차단:&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;SetEnvIfNoCase User-Agent &quot;badbot&quot; bad_bot
Order allow,deny
Allow from all
Deny from env=bad_bot&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;8. Basic 인증 (간단한 비밀번호 보호)&lt;/h3&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;AuthType Basic
AuthName &quot;Restricted Area&quot;
AuthUserFile /path/to/.htpasswd
Require valid-user&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;.htpasswd&lt;/code&gt; 파일은 &lt;code&gt;htpasswd&lt;/code&gt; 명령으로 생성:&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;htpasswd -c .htpasswd username&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실무 주의사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;.htaccess&lt;/code&gt; 문법 오류가 있으면 500 에러가 나며 사이트 전체가 먹통됩니다. 수정 전 백업 필수!&lt;/li&gt;
&lt;li&gt;Nginx 서버는 &lt;code&gt;.htaccess&lt;/code&gt;를 지원하지 않습니다. &lt;code&gt;nginx.conf&lt;/code&gt;에서 직접 설정해야 합니다.&lt;/li&gt;
&lt;li&gt;공유 호스팅에서는 일부 설정이 제한될 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;디버깅 팁&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문법 오류로 500 에러가 났다면:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Apache 에러 로그 확인: &lt;code&gt;/var/log/apache2/error.log&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;마지막 수정 부분 되돌리기&lt;/li&gt;
&lt;li&gt;로컬 또는 테스트 서버에서 먼저 테스트&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 조치로 큰 보안 효과를 낼 수 있습니다. &lt;code&gt;.htaccess&lt;/code&gt; 몇 줄이 해킹 시도를 차단합니다. 5분 투자로 몇백만 원 피해를 막습니다. 유지보수는 작은 방패입니다.&lt;/p&gt;</description>
      <category>PHP 실무</category>
      <author>프리터코더</author>
      <guid isPermaLink="true">https://freetercoder.tistory.com/97</guid>
      <comments>https://freetercoder.tistory.com/97#entry97comment</comments>
      <pubDate>Fri, 25 Sep 2026 14:33:33 +0900</pubDate>
    </item>
    <item>
      <title>홈페이지 해킹, 생각보다 가까이 있다</title>
      <link>https://freetercoder.tistory.com/95</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;우리 같은 작은 회사를 누가 해킹하겠어요?&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 대표님들이 이렇게 생각하십니다. 대기업도 아니고, 개인정보도 많지 않고, 특별히 중요한 데이터도 없는데 해커가 관심이나 있겠냐는 겁니다.&lt;br /&gt;하지만 현실은 다릅니다. 해킹은 대기업만의 문제가 아닙니다. 오히려 보안이 허술한 중소기업이 더 쉬운 타깃이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;홈페이지 해킹이 비즈니스에 미치는 영향은 생각보다 큽니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;고객 개인정보가 유출되면 법적 책임이 따릅니다. 개인정보보호법에 따라 과태료가 부과되고, 피해 고객에게 보상해야 할 수도 있습니다. 중소기업에게 수천만 원의 과태료는 치명적입니다.&lt;/li&gt;
&lt;li&gt;사이트가 스팸 사이트로 전락합니다. 해커들은 홈페이지를 불법 도박 사이트나 성인 사이트 광고판으로 바꿔버립니다. 고객이 접속했다가 &quot;이게 무슨 사이트야?&quot; 하며 놀라서 나갑니다.&lt;/li&gt;
&lt;li&gt;검색엔진에서 퇴출됩니다. 구글은 해킹된 사이트를 &quot;위험한 사이트&quot;로 표시하고, 검색 결과에서 제외합니다. 몇 년 동안 쌓아온 SEO 순위가 하루아침에 날아갑니다.&lt;/li&gt;
&lt;li&gt;브랜드 신뢰가 무너집니다. &quot;저 회사 홈페이지가 해킹당했대&quot;라는 소문이 돌면, 고객들은 &quot;보안도 제대로 못 하는 회사&quot;라고 인식합니다. 신뢰 회복에는 몇 배의 시간과 비용이 듭니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 한 중소 쇼핑몰에서 있었던 일입니다. 어느 날 고객이 &quot;홈페이지에 이상한 팝업이 뜨는데 괜찮나요?&quot;라는 문의를 남겼습니다.&lt;br /&gt;확인해보니 사이트 전체가 불법 도박 광고로 도배돼 있었습니다.&lt;br /&gt;급하게 호스팅 업체에 연락했지만, &quot;해킹 복구는 저희 업무가 아닙니다&quot;라는 답변만 돌아왔습니다.&lt;br /&gt;전문 복구 업체를 찾으니 견적이 500만 원. 게다가 복구까지 1주일이 걸린다고 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 1주일 동안 사이트는 폐쇄됐고, 매출은 제로. 고객들은 &quot;저 사이트 해킹당했대, 위험해&quot; 하며 떠났습니다.&lt;br /&gt;복구 후에도 구글에서 &quot;위험한 사이트&quot; 경고가 몇 주간 유지됐고, 트래픽은 절반으로 줄었습니다.&lt;br /&gt;결국 직접적 손실(복구 비용 500만 원 + 매출 손실 700만 원) + 간접 손실(고객 이탈, SEO 하락)을 합치면 1,500만 원이 넘는 피해를 입었습니다.&lt;br /&gt;예방에 월 20만 원만 투자했더라면 막을 수 있었던 사고였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 이런 일이 생길까요?&lt;br /&gt;중소기업 홈페이지는 보안에 취약한 경우가 많습니다. 몇 년 전에 만들어진 CMS(워드프레스, 그누보드 등)를 업데이트 없이 그대로 쓰는 경우가 많은데, 이런 시스템엔 알려진 보안 취약점이 가득합니다.&lt;br /&gt;해커들은 자동화된 프로그램으로 취약한 사이트를 찾아다니며 무차별 공격합니다. 대기업을 노리는 게 아니라, &quot;쉽게 뚫리는 곳&quot;을 노립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 관리자 비밀번호가 약하거나(&lt;code&gt;admin&lt;/code&gt;, &lt;code&gt;1234&lt;/code&gt; 등), 파일 업로드 기능에 보안 검증이 없거나, SQL Injection 방어가 안 된 경우가 많습니다.&lt;br /&gt;이런 취약점은 해커 입장에서 &quot;열린 문&quot;이나 마찬가지입니다. 그냥 들어가서 파일을 업로드하고, 관리자 권한을 탈취하고, 데이터를 털어갑니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 서버 자체가 오래돼서 보안 패치가 안 된 경우도 많습니다. PHP 5.x 같은 낡은 버전은 이미 보안 지원이 끝났기 때문에, 새로운 취약점이 발견돼도 패치가 나오지 않습니다. 이런 서버는 해커들에게 &quot;공짜 타깃&quot;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유지보수 전문가는 이런 보안 위협을 예방합니다. 정기적으로 CMS와 플러그인을 업데이트하고, 서버 보안 패치를 진행합니다.&lt;br /&gt;관리자 비밀번호를 강력하게 설정하고, 파일 업로드 기능에 검증 로직을 추가합니다. SQL Injection, XSS 같은 전형적인 공격을 방어하는 코드를 삽입합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 서버 접근 로그를 모니터링해서 의심스러운 접속 시도를 감지합니다. &quot;어? 새벽 3시에 중국 IP에서 관리자 페이지 로그인 시도가 100번?&quot; 이런 패턴이 보이면 즉시 IP를 차단하고, 방화벽 규칙을 강화합니다. 예방이 최고의 방어입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백업 체계도 중요합니다. 만약 해킹을 당해도 깨끗한 백업이 있다면, 빠르게 복구할 수 있습니다. 하루 1회 자동 백업을 설정하고, 백업 파일은 별도 서버에 보관합니다. 이것만으로도 피해를 최소화할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보안 취약점은 현실적으로 흔합니다. &quot;우리 회사는 괜찮겠지&quot;라는 생각이 가장 위험합니다.&lt;br /&gt;해킹은 먼 나라 이야기가 아니라, 지금 이 순간에도 일어나고 있는 현실입니다. 작은 투자로 큰 사고를 막을 수 있습니다.&lt;br /&gt;보안은 사치가 아니라 필수입니다.&lt;/p&gt;</description>
      <category>PHP 유지보수</category>
      <author>프리터코더</author>
      <guid isPermaLink="true">https://freetercoder.tistory.com/95</guid>
      <comments>https://freetercoder.tistory.com/95#entry95comment</comments>
      <pubDate>Tue, 22 Sep 2026 14:31:19 +0900</pubDate>
    </item>
  </channel>
</rss>