WordPress Bakım Modunda Belirli IP Adreslerine İzin Verme

Site bakıma alındığında müşteriler sayfayı göremesin ama sen geliştirmeye devam edebileyim istiyorsun. Klasik senaryo. WordPress’te bunu doğru yapmak sandığından biraz daha ince bir iş, çünkü hem bakım modunun kendisini kontrol etmen gerekiyor hem de hangi IP’lerin bypass edebileceğini yönetmen gerekiyor. Gelin bunu functions.php üzerinden nasıl düzgünce yapacağımıza bakalım.

Bakım Modu Nedir ve Neden IP Filtresi Gerekir?

WordPress’in yerleşik bakım modu .maintenance dosyasıyla çalışır. Güncelleme sırasında bu dosya otomatik oluşturulur ve ziyaretçilere 503 HTTP durum koduyla “Briefly unavailable for scheduled maintenance” mesajı gösterilir. Ama bu yerleşik bakım modu kısa sürelidir ve üzerinde tam kontrol sahibi değilsindir.

Gerçek dünyada şu senaryolarla karşılaşırsın:

  • WooCommerce mağazasını yeniden tasarlıyorsun, müşteriler yarım kalmış ürün sayfalarını görmesin ama sipariş departmanı canlı ortamda test yapmaya devam etsin
  • Bir ajans olarak müşterinin sitesini geliştiriyorsun, müşteri kendi ofisinden siteyi önizleyebilsin ama son kullanıcılar görmemeli
  • Staging ortanın olmadığı durumlarda production üzerinde acil bir değişiklik yapıyorsun ve siteyi tamamen kapatmak istemiyorsun
  • Sunucu taşıması sırasında DNS henüz tam olarak propagate olmamışken eski sunucuda bakım gösterirken yeni sunucuya erişimin olsun

Bu durumlarda ihtiyacın olan şey: belirli IP adreslerinin bakım modunu görmeden siteye erişebilmesi.

Temel Yapı: functions.php’ye Ne Yazacağız?

WordPress’te bakım modunu functions.php üzerinden yönetmenin en temiz yolu wp_maintenance action hook’unu kullanmaktır. Bu hook, WordPress bakım modunu tetiklemeden hemen önce çalışır ve burada die() çağrısını durdurabilirsin.

Ama daha kapsamlı bir çözüm için template_redirect hook’unu da kullanabiliriz. Gelin adım adım gidelim.

Basit IP Whitelist ile Bakım Modu

İlk ve en temel örnek:

// functions.php

function custom_maintenance_mode() {
    // Yönetici giriş yapmışsa bakım modunu gösterme
    if ( is_user_logged_in() && current_user_can( 'manage_options' ) ) {
        return;
    }

    // İzin verilen IP adresleri listesi
    $allowed_ips = array(
        '85.123.45.67',   // Ofis IP'si
        '95.12.34.56',    // Ev IP'si
        '176.45.67.89',   // Ajans ofisi
    );

    // Ziyaretçinin IP adresini al
    $visitor_ip = $_SERVER['REMOTE_ADDR'];

    // IP whitelist'te varsa bakım modunu gösterme
    if ( in_array( $visitor_ip, $allowed_ips ) ) {
        return;
    }

    // Bakım modunu göster
    wp_die(
        '<h1>Bakımda Olduğumuz İçin Üzgünüz</h1>
         <p>Sitemiz şu anda bakımdadır. Kısa süre içinde geri döneceğiz.</p>',
        'Bakım Modu',
        array( 'response' => 503 )
    );
}
add_action( 'template_redirect', 'custom_maintenance_mode' );

Bu temel yapı çalışır ama production’da birkaç eksikliği var. Şimdi bunları geliştirelim.

Proxy ve Load Balancer Arkasında Doğru IP Tespiti

Birçok WordPress sitesi Cloudflare, Nginx reverse proxy veya load balancer arkasında çalışır. Bu durumlarda $_SERVER['REMOTE_ADDR'] gerçek ziyaretçi IP’sini değil, proxy sunucusunun IP’sini döndürür. Bu yüzden IP tespitini daha güvenilir hale getirmemiz gerekir.

// functions.php

function get_real_visitor_ip() {
    $ip_keys = array(
        'HTTP_CF_CONNECTING_IP',     // Cloudflare
        'HTTP_X_FORWARDED_FOR',      // Genel proxy/load balancer
        'HTTP_X_REAL_IP',            // Nginx
        'HTTP_X_FORWARDED',
        'HTTP_FORWARDED_FOR',
        'REMOTE_ADDR'                // Son çare, direkt bağlantı
    );

    foreach ( $ip_keys as $key ) {
        if ( ! empty( $_SERVER[ $key ] ) ) {
            // X-Forwarded-For birden fazla IP içerebilir, ilkini al
            $ip = explode( ',', $_SERVER[ $key ] );
            $ip = trim( $ip[0] );

            // Geçerli bir IP mi kontrol et
            if ( filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) ) {
                return $ip;
            }
        }
    }

    return $_SERVER['REMOTE_ADDR'];
}

Dikkat etmen gereken nokta: HTTP_X_FORWARDED_FOR başlığı kullanıcı tarafından manipüle edilebilir. Eğer güvenlik kritikse bu başlığı güvenilir proxy’lerden geldiğini doğruladıktan sonra kullanmalısın. Basit bir bakım modu bypass için bu seviye yeterli olsa da, dikkatli olmakta fayda var.

CIDR Notasyonuyla Subnet Desteği

Bazen tek bir IP yerine bir IP bloğuna izin vermek isteyebilirsin. Örneğin ofisin /24 subnet’i veya bir VPN ağı. WordPress core’da bunu yapan bir fonksiyon yok, kendimiz yazacağız.

// functions.php

function ip_in_cidr( $ip, $cidr ) {
    if ( strpos( $cidr, '/' ) === false ) {
        // CIDR değil, düz IP karşılaştırması
        return ( $ip === $cidr );
    }

    list( $subnet, $mask ) = explode( '/', $cidr );

    $ip_long     = ip2long( $ip );
    $subnet_long = ip2long( $subnet );
    $mask_long   = -1 << ( 32 - (int) $mask );

    return ( ( $ip_long & $mask_long ) === ( $subnet_long & $mask_long ) );
}

function is_ip_whitelisted( $visitor_ip, $allowed_list ) {
    foreach ( $allowed_list as $entry ) {
        if ( ip_in_cidr( $visitor_ip, $entry ) ) {
            return true;
        }
    }
    return false;
}

Bu iki fonksiyonla artık hem tekil IP hem de subnet tanımlayabilirsin:

// functions.php - Kullanım örneği

$allowed_ips = array(
    '85.123.45.67',        // Tekil IP - Müşteri ofisi
    '192.168.1.0/24',      // Subnet - İç ağ
    '10.0.0.0/8',          // Geniş subnet - VPN ağı
    '176.45.67.89',        // Tekil IP - Geliştirici
);

$visitor_ip = get_real_visitor_ip();

if ( is_ip_whitelisted( $visitor_ip, $allowed_ips ) ) {
    // Bakım modunu gösterme
    return;
}

Kapsamlı Bakım Modu Fonksiyonu

Şimdiye kadar yazdığımız tüm parçaları birleştirelim ve üzerine birkaç özellik daha ekleyelim. Bu versiyonda:

  • Admin kullanıcılar her zaman bypass eder
  • IP whitelist desteği
  • Subnet desteği
  • Özel bakım sayfası tasarımı
  • Geri sayım seçeneği
  • WooCommerce webhook’larını koruma
// functions.php - Kapsamlı bakım modu sistemi

/**
 * Bakım Modu Ayarları
 * Bu diziyi ihtiyacına göre düzenle
 */
function get_maintenance_config() {
    return array(
        'enabled'      => true,   // true/false ile kolayca aç/kapat
        'allowed_ips'  => array(
            '85.123.45.67',       // Ofis
            '78.90.12.34',        // Evden çalışma
            '192.168.0.0/16',     // Yerel ağ
        ),
        'allowed_user_roles' => array( 'administrator', 'editor' ),
        'bypass_woocommerce'  => true,   // WC API ve webhook'ları bypass et
        'maintenance_title'   => 'Çok Yakında',
        'maintenance_message' => 'Daha iyi bir deneyim için sitemizi yeniliyoruz. Kısa süre içinde geri döneceğiz.',
        'contact_email'       => '[email protected]',
    );
}

function advanced_maintenance_mode() {
    $config = get_maintenance_config();

    // Bakım modu aktif mi?
    if ( ! $config['enabled'] ) {
        return;
    }

    // Admin paneli istekleri bypass
    if ( is_admin() ) {
        return;
    }

    // WP-CLI üzerinden çalışıyorsa bypass
    if ( defined( 'WP_CLI' ) && WP_CLI ) {
        return;
    }

    // Cron işleri bypass
    if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
        return;
    }

    // WooCommerce API ve webhook bypass
    if ( $config['bypass_woocommerce'] ) {
        if ( isset( $_SERVER['REQUEST_URI'] ) ) {
            $uri = $_SERVER['REQUEST_URI'];
            if ( strpos( $uri, '/wc-api/' ) !== false ||
                 strpos( $uri, '/wp-json/wc/' ) !== false ||
                 strpos( $uri, '?wc-ajax=' ) !== false ) {
                return;
            }
        }
    }

    // Giriş yapmış kullanıcı rolü kontrolü
    if ( is_user_logged_in() ) {
        $user = wp_get_current_user();
        foreach ( $config['allowed_user_roles'] as $role ) {
            if ( in_array( $role, (array) $user->roles ) ) {
                return;
            }
        }
    }

    // IP kontrolü
    $visitor_ip = get_real_visitor_ip();
    if ( is_ip_whitelisted( $visitor_ip, $config['allowed_ips'] ) ) {
        return;
    }

    // Bakım sayfasını göster
    show_maintenance_page( $config );
}
add_action( 'template_redirect', 'advanced_maintenance_mode', 1 );

function show_maintenance_page( $config ) {
    http_response_code( 503 );
    header( 'Retry-After: 3600' );
    header( 'Content-Type: text/html; charset=UTF-8' );
    ?>
    <!DOCTYPE html>
    <html lang="tr">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title><?php echo esc_html( $config['maintenance_title'] ); ?></title>
        <style>
            * { margin: 0; padding: 0; box-sizing: border-box; }
            body {
                font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
                background: #0f0f0f;
                color: #ffffff;
                display: flex;
                align-items: center;
                justify-content: center;
                min-height: 100vh;
                text-align: center;
                padding: 20px;
            }
            .container { max-width: 600px; }
            h1 { font-size: 2.5rem; margin-bottom: 1rem; color: #f0f0f0; }
            p { font-size: 1.1rem; color: #aaa; line-height: 1.6; margin-bottom: 1.5rem; }
            a { color: #667eea; text-decoration: none; }
        </style>
    </head>
    <body>
        <div class="container">
            <h1><?php echo esc_html( $config['maintenance_title'] ); ?></h1>
            <p><?php echo esc_html( $config['maintenance_message'] ); ?></p>
            <p>İletişim: <a href="mailto:<?php echo esc_attr( $config['contact_email'] ); ?>"><?php echo esc_html( $config['contact_email'] ); ?></a></p>
        </div>
    </body>
    </html>
    <?php
    exit;
}

wp-login.php Erişimini Koruma

Bakım modundayken normal kullanıcılar giriş yapmaya çalışabilir. Bunu da kontrol etmemiz mantıklı. Öte yandan whitelist’teki IP’lerin giriş sayfasına erişebilmesi gerekiyor.

// functions.php - Login sayfası için ek koruma

function maintenance_login_check() {
    $config = get_maintenance_config();

    if ( ! $config['enabled'] ) {
        return;
    }

    // Sadece wp-login.php için çalış
    if ( ! isset( $GLOBALS['pagenow'] ) || $GLOBALS['pagenow'] !== 'wp-login.php' ) {
        return;
    }

    // Logout işlemine izin ver
    if ( isset( $_GET['action'] ) && $_GET['action'] === 'logout' ) {
        return;
    }

    $visitor_ip = get_real_visitor_ip();

    // Whitelist'te varsa geç
    if ( is_ip_whitelisted( $visitor_ip, $config['allowed_ips'] ) ) {
        return;
    }

    // Whitelist'te yoksa login sayfasını da kapat
    wp_die(
        'Giriş sayfasına şu anda erişilemiyor. Sitemiz bakımdadır.',
        'Erişim Engellendi',
        array( 'response' => 503 )
    );
}
add_action( 'init', 'maintenance_login_check' );

Bakım Modunu Dashboard’dan Açıp Kapatma

Her bakım gerektiğinde functions.php’yi düzenlemek zahmetli. Basit bir admin seçeneği ekleyelim. Bu kod WordPress Options API’sini kullanır, herhangi bir eklenti gerekmez.

// functions.php - Admin kontrol paneli

function maintenance_admin_menu() {
    add_options_page(
        'Bakım Modu',
        'Bakım Modu',
        'manage_options',
        'maintenance-mode',
        'maintenance_admin_page'
    );
}
add_action( 'admin_menu', 'maintenance_admin_menu' );

function maintenance_admin_page() {
    if ( isset( $_POST['save_maintenance'] ) && check_admin_referer( 'maintenance_mode_nonce' ) ) {
        $status = isset( $_POST['maintenance_enabled'] ) ? '1' : '0';
        $ips    = sanitize_textarea_field( $_POST['maintenance_ips'] );
        update_option( 'custom_maintenance_enabled', $status );
        update_option( 'custom_maintenance_ips', $ips );
        echo '<div class="updated"><p>Ayarlar kaydedildi.</p></div>';
    }

    $enabled = get_option( 'custom_maintenance_enabled', '0' );
    $ips     = get_option( 'custom_maintenance_ips', '' );
    ?>
    <div class="wrap">
        <h1>Bakım Modu Ayarları</h1>
        <form method="post">
            <?php wp_nonce_field( 'maintenance_mode_nonce' ); ?>
            <table class="form-table">
                <tr>
                    <th>Bakım Modu</th>
                    <td>
                        <label>
                            <input type="checkbox" name="maintenance_enabled" value="1" <?php checked( $enabled, '1' ); ?>>
                            Bakım modunu etkinleştir
                        </label>
                    </td>
                </tr>
                <tr>
                    <th>İzin Verilen IP'ler</th>
                    <td>
                        <textarea name="maintenance_ips" rows="5" cols="40"><?php echo esc_textarea( $ips ); ?></textarea>
                        <p class="description">Her satıra bir IP veya CIDR bloğu girin. Örn: 85.123.45.67 veya 192.168.1.0/24</p>
                    </td>
                </tr>
            </table>
            <?php submit_button( 'Ayarları Kaydet', 'primary', 'save_maintenance' ); ?>
        </form>
    </div>
    <?php
}

// Config fonksiyonunu database'den okuyacak şekilde güncelle
function get_maintenance_config_dynamic() {
    $enabled     = get_option( 'custom_maintenance_enabled', '0' );
    $ips_raw     = get_option( 'custom_maintenance_ips', '' );
    $ips_array   = array_filter( array_map( 'trim', explode( "n", $ips_raw ) ) );

    return array(
        'enabled'     => ( $enabled === '1' ),
        'allowed_ips' => $ips_array,
        // Diğer config değerleri...
    );
}

WP-CLI ile Bakım Modu Yönetimi

Sunucuya SSH bağlantısı varsa ve terminal üzerinden bakım modunu kontrol etmek istiyorsan, WP-CLI komutlarını kullanabilirsin. Bu özellikle deployment scriptlerinde işe yarıyor.

# WP-CLI ile bakım modunu açma (database option'ı güncelle)
wp option update custom_maintenance_enabled 1 --path=/var/www/html

# WP-CLI ile bakım modunu kapatma
wp option update custom_maintenance_enabled 0 --path=/var/www/html

# Mevcut durumu kontrol etme
wp option get custom_maintenance_enabled --path=/var/www/html

# Deployment sırasında otomatik kullanım
wp option update custom_maintenance_enabled 1
# ... deployment işlemleri ...
wp cache flush
wp option update custom_maintenance_enabled 0
echo "Deployment tamamlandı, bakım modu kapatıldı."

Bu yaklaşım özellikle CI/CD pipeline’larına entegre etmek istediğinde çok kullanışlı. GitHub Actions veya GitLab CI içinde SSH üzerinden bu komutları çalıştırabilirsin.

Dikkat Edilmesi Gereken Güvenlik Noktaları

Bakım modu bypass sistemini kurarken birkaç güvenlik detayına dikkat etmen gerekiyor.

IP Spoofing riski: HTTP_X_FORWARDED_FOR başlığı istemci tarafından manipüle edilebilir. Eğer uygulamanın önünde güvenilir bir proxy varsa, bu başlığı sadece o proxy’den gelen isteklerde kullan.

Statik IP sorunu: Birçok internet sağlayıcısı dinamik IP dağıtır. Yani bugün whitelist’e eklediğin IP yarın başkasına atanmış olabilir. Bu yüzden önemli erişimler için VPN kullanmak ya da en azından giriş yapmış kullanıcı kontrolüne güvenmek daha sağlıklı.

Cache problemi: Eğer Cloudflare, W3 Total Cache veya WP Super Cache gibi bir caching sistemi kullanıyorsan, bakım sayfası önbelleğe alınabilir. 503 response header’ı doğru set ettiğinden emin ol ve caching sisteminin 503 yanıtlarını önbelleğe almadığını kontrol et.

REST API endpoint’leri: WordPress REST API, template_redirect hook’undan geçmez. REST API endpoint’lerini de korumak istiyorsan ayrıca rest_authentication_errors veya rest_pre_dispatch hook’larına bakman gerekiyor.

// functions.php - REST API koruması

function maintenance_rest_api_check( $result ) {
    $config = get_maintenance_config();

    if ( ! $config['enabled'] ) {
        return $result;
    }

    // WooCommerce API isteklerini bypass et
    $route = isset( $GLOBALS['wp']->query_vars['rest_route'] ) ? $GLOBALS['wp']->query_vars['rest_route'] : '';
    if ( strpos( $route, '/wc/' ) === 0 ) {
        return $result;
    }

    $visitor_ip = get_real_visitor_ip();
    if ( is_ip_whitelisted( $visitor_ip, $config['allowed_ips'] ) ) {
        return $result;
    }

    if ( is_user_logged_in() && current_user_can( 'manage_options' ) ) {
        return $result;
    }

    return new WP_Error(
        'maintenance_mode',
        'Site şu anda bakımdadır.',
        array( 'status' => 503 )
    );
}
add_filter( 'rest_pre_dispatch', 'maintenance_rest_api_check' );

Gerçek Dünya Senaryosu: WooCommerce Mağazası Yenileme

Diyelim ki bir müşterinin WooCommerce mağazasını canlıda yeniliyorsun. Müşteri kendi ofisinden siparişlere bakmaya devam etmesi gerekiyor, sen geliştirici olarak çalışacaksın, ama son kullanıcılar bakım sayfası görüyor olacak.

Bu senaryoda tam config şöyle olur:

  • Müşteri ofis IP’si whitelist’e eklenmiş durumda
  • Senin geliştirici IP’n eklenmiş
  • WooCommerce webhook’ları (ödeme bildirimler, kargo güncellemeleri) bypass edilmiş
  • Administrator ve shop_manager rolleri her zaman bypass ediyor
  • Bakım sayfasında mağaza logosu ve tahmini açılış tarihi yazıyor
  • 503 status kodu ile birlikte Retry-After header’ı set edilmiş, böylece arama motorları siteyi indeksten silmiyor

Bu konfigürasyonu kurduktan sonra ne sen ne de müşteri, aktif siparişler ve entegrasyonlar zarar görmeden, ziyaretçilere farklı bir deneyim sunabilirsiniz.

Sonuç

WordPress’te IP bazlı bakım modu bypass sistemi kurmak, ilk bakışta karmaşık görünse de birkaç temiz fonksiyonla gayet güzel çözülebilir. Önemli olan noktaları özetlersek:

  • IP tespitini doğru yap, proxy ve load balancer senaryolarını göz önüne al
  • CIDR desteği ekle, tekil IP’ler çoğu zaman yetmez
  • WooCommerce ve REST API endpoint’lerini ayrıca koru, sadece template_redirect yeterli değil
  • Cache sistemleriyle uyumluluğu kontrol et, 503 header’ları doğru gitmeli
  • WP-CLI ile entegre et, deployment otomasyonunda büyük kolaylık sağlar
  • Admin arayüzü ekle, her seferinde functions.php düzenleme zahmetinden kurtulursun

Bu sistemi bir kez kurduğunda, sonraki her bakımda sadece IP listesini güncelleyip toggle’a basıyorsun. Saatlerce süren bakım süreçleri artık ne müşteriyi ne de seni gereksiz yere strese sokuyor.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir