Share via

Reverse Proxy using WebApp for different WebApp hosting Wordpress application

Aryaa Kotian 85 Reputation points
2025-11-12T09:10:59.3966667+00:00

I have a wordpress website hosted in app service in azure and I want to use reverse proxy rule for all pages in web tier. web tier - webtier.idealake.com and app tier - apptier.idealake.com
If public user will access web tier, all pages should be rewritten but only wp-admin page should redirect back to home page that is webtier.idealake.com
Also app tier should not get affected by these rules as there it should open everything as it is
For now I have used the "ReverseProxy(1.0.4) by Eelco Koster, Jerome Haltom" this extension on web tier. and below is the rule used in the web.config file of the web.tier
web.config -

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <rewrite>
      <rules>
        <!-- 1️ Redirect /wp-admin to home page -->
        <rule name="Block wp-admin for public" stopProcessing="true">
          <match url="^wp-admin(/.*)?$" ignoreCase="true" />
          <action type="Redirect" url="https://webwp.idealake.com/" redirectType="Found" />
        </rule>
        <!-- 2️ Reverse proxy all other requests to app tier -->
        <rule name="ReverseProxyToAppTier" stopProcessing="true">
          <match url="(.*)" />
          <action type="Rewrite" url="https://wp.idealake.com/{R:1}" />
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>
```and also on the App tier, i have updated the wp-config.php file as mentioned below:

```php
<?php
/** Enable W3 Total Cache */
define('WP_CACHE', true); // Added by W3 Total Cache


/**
 * The base configuration for WordPress
 *
 * The wp-config.php creation script uses this file during the installation.
 * You don't have to use the web site, you can copy this file to "wp-config.php"
 * and fill in the values.
 *
 * This file contains the following configurations:
 *
 * * Database settings
 * * Secret keys
 * * Database table prefix
 * * ABSPATH
 *
 * @link https://wordpress.org/support/article/editing-wp-config-php/
 *
 * @package WordPress
 */

//Using environment variables for memory limits
$wp_memory_limit = (getenv('WP_MEMORY_LIMIT') && preg_match("/^[0-9]+M$/", getenv('WP_MEMORY_LIMIT'))) ? getenv('WP_MEMORY_LIMIT') : '128M';
$wp_max_memory_limit = (getenv('WP_MAX_MEMORY_LIMIT') && preg_match("/^[0-9]+M$/", getenv('WP_MAX_MEMORY_LIMIT'))) ? getenv('WP_MAX_MEMORY_LIMIT') : '256M';

/** General WordPress memory limit for PHP scripts*/
define('WP_MEMORY_LIMIT', $wp_memory_limit );

/** WordPress memory limit for Admin panel scripts */
define('WP_MAX_MEMORY_LIMIT', $wp_max_memory_limit );


//Using environment variables for DB connection information

// ** Database settings - You can get this info from your web host ** //
$connectstr_dbhost = getenv('DATABASE_HOST');
$connectstr_dbname = getenv('DATABASE_NAME');
$connectstr_dbusername = getenv('DATABASE_USERNAME');
$connectstr_dbpassword = getenv('DATABASE_PASSWORD');

// Using managed identity to fetch MySQL access token
if (strtolower(getenv('ENABLE_MYSQL_MANAGED_IDENTITY')) === 'true') {
	try {
		require_once(ABSPATH . 'class_entra_database_token_utility.php');
		if (strtolower(getenv('CACHE_MYSQL_ACCESS_TOKEN')) !== 'true') {
			$connectstr_dbpassword = EntraID_Database_Token_Utilities::getAccessToken();
		} else {
			$connectstr_dbpassword = EntraID_Database_Token_Utilities::getOrUpdateAccessTokenFromCache();
		}
	} catch (Exception $e) {
		// An empty string displays a 502 HTTP error page rather than a database connection error page. So, using a dummy string instead.
		$connectstr_dbpassword = '<dummy-value>';
		error_log($e->getMessage());
	}
}

/** The name of the database for WordPress */
define('DB_NAME', $connectstr_dbname);

/** MySQL database username */
define('DB_USER', $connectstr_dbusername);

/** MySQL database password */
define('DB_PASSWORD',$connectstr_dbpassword);

/** MySQL hostname */
define('DB_HOST', $connectstr_dbhost);

/** Database Charset to use in creating database tables. */
define( 'DB_CHARSET', 'utf8' );

/** The Database Collate type. Don't change this if in doubt. */
define( 'DB_COLLATE', '' );

/** Enabling support for connecting external MYSQL over SSL*/
$mysql_sslconnect = (getenv('DB_SSL_CONNECTION')) ? getenv('DB_SSL_CONNECTION') : 'true';
if (strtolower($mysql_sslconnect) != 'false' && !is_numeric(strpos($connectstr_dbhost, "127.0.0.1")) && !is_numeric(strpos(strtolower($connectstr_dbhost), "localhost"))) {
	define('MYSQL_CLIENT_FLAGS', MYSQLI_CLIENT_SSL);
}


/**#@+
 * Authentication Unique Keys and Salts.
 *
 * Change these to different unique phrases!
 * You can generate these using the {@link https://api.wordpress.org/secret-key/1.1/salt/ WordPress.org secret-key service}
 * You can change these at any point in time to invalidate all existing cookies. This will force all users to have to log in again.
 *
 * @since 2.6.0
 */
define( 'AUTH_KEY',         '1D>Vjw< -J(6`)c#9rAx3nTjJ)I?qGb18tC4,20xhDQ}&/:|IWu.0]75%B0>}@?K' );
define( 'SECURE_AUTH_KEY',  'wAs^3>pPs2Xoe(&*StX82yjMp`Au=FgjhaQhGjo5@QrGu:d(N2oy#]6oo|nSj4Oq' );
define( 'LOGGED_IN_KEY',    'McyO7m#2iY&xF{;^b6*0$hk&6R7yH4<-g`C-0kG~Ue<ZH3b<9Qs=g:B*b*.N$_LA' );
define( 'NONCE_KEY',        'Gp5FPYoh8!g]6sLimi.d]N547r(coztOV$)b0i(A4:7]8rMMyLJ7~a_&Zhm}#qA*' );
define( 'AUTH_SALT',        'P+H<b/|TZVTPG8TI?o~K`T`<h>V g:X)=#[!3)_-k]!+xN,wQ$Dz@jo?7X5t`Fu0' );
define( 'SECURE_AUTH_SALT', 'K7$vel@^:l&dX8gL)P>2gda=GLWF9K&G!FBN)a~me^q?;g[?c++7WQODUYcP2%|X' );
define( 'LOGGED_IN_SALT',   'KG*s~Q0OC!9oFw6|TSgAm[z9V-;pc@c8F}u@ J*rtlIF>vx[SyE:CKGmAe[Up6=W' );
define( 'NONCE_SALT',       'CnqcC[CH4{]CwN];7,=sbTJg:#k|(vt,9exUvS.7]?xv+0}sw$@k=zClI]GL{/]Y' );

/**#@-*/

/**
 * WordPress Database Table prefix.
 *
 * You can have multiple installations in one database if you give each
 * a unique prefix. Only numbers, letters, and underscores please!
 */
$table_prefix = 'wp_';

/**
 * For developers: WordPress debugging mode.
 *
 * Change this to true to enable the display of notices during development.
 * It is strongly recommended that plugin and theme developers use WP_DEBUG
 * in their development environments.
 *
 * For information on other constants that can be used for debugging,
 * visit the documentation.
 *
 * @link https://wordpress.org/support/article/debugging-in-wordpress/
 */
define( 'WP_DEBUG', false );

/* That's all, stop editing! Happy blogging. */
/**https://developer.wordpress.org/reference/functions/is_ssl/ */
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https')
	$_SERVER['HTTPS'] = 'on';

$http_protocol='http://';
if (!preg_match("/^localhost(:[0-9])*/", $_SERVER['HTTP_HOST']) && !preg_match("/^127\.0\.0\.1(:[0-9])*/", $_SERVER['HTTP_HOST'])) {
	$http_protocol='https://';
}

//Relative URLs for swapping across app service deployment slots
// Detect if request came through reverse proxy domain
define('WP_HOME', 'https://webtier.idealake.com');
define('WP_SITEURL', 'https://webtier.idealake.com');
define('WP_CONTENT_URL', '/wp-content');
define('DOMAIN_CURRENT_SITE', $_SERVER['HTTP_HOST']);

/** Absolute path to the WordPress directory. */
if ( ! defined( 'ABSPATH' ) ) {
	define( 'ABSPATH', __DIR__ . '/' );
}

/** Sets up WordPress vars and included files. */
require_once ABSPATH . 'wp-settings.php';

```If i hit webtier.idealake.com, the home page is reflecting but, any other page if browsed, it will redirect to apptier domain with 200 status (no 301 or redirect status)  
Please note as of now this is the default wordpress sample being used that we get from Wordpress on app service option  
And as we are using that extension on web tier, in the web tier, we only have a web.config file where we add our rule  
  
Are we saying the reverse proxy can not work by using WebApp (App Service)
Azure App Service
Azure App Service

Azure App Service is a service used to create and deploy scalable, mission-critical web apps.

0 comments No comments

1 answer

Sort by: Most helpful
  1. Jerald Felix 12,695 Reputation points Volunteer Moderator
    2025-11-13T03:03:40.07+00:00

    Hello Aryaa Kotian,

    The redirect issue in your WordPress reverse proxy setup where the homepage loads correctly on webtier.idealake.com but other pages bounce to apptier.idealake.com with a 200 status (no 301 redirect) happens because the URL Rewrite rule in web.config isn't properly preserving the original host header or handling WordPress's internal URL generation, causing the backend app tier to respond with absolute URLs pointing to itself instead of the proxy domain. The wp-admin redirect to home is working as intended, but the 200 status on other pages indicates internal rewrites are leaking through without proxy masking. Azure App Service absolutely supports reverse proxies between two apps using the ReverseProxy extension and URL Rewrite it's a standard pattern for web/app tier separation but your rules need tweaks for WordPress's relative/absolute URL logic and header forwarding.

    Root Cause

    • Host Header Leakage: WordPress on the app tier generates links/forms based on its own domain (apptier.idealake.com) since the request host isn't forwarded correctly, leading to "internal" 200 responses that browsers interpret as valid but with wrong origins.
    • No Outbound Rules: Inbound rewrites forward requests, but responses from app tier contain unrewritten URLs (e.g., in HTML links, Location headers), causing the leak.
    • Extension Limitations: ReverseProxy(1.0.4) handles basic forwarding but needs ARR (Application Request Routing) enabled for full header preservation and outbound rewriting in IIS on Azure.

    Step-by-Step Fix

    1. Enable ARR and Update Web.config on Web Tier

    First, ensure ARR is active (it's bundled in Azure App Service but proxy mode needs explicit config). Install/enable via Kudu if needed (Site Extensions > ARR), then revise your web.config for bidirectional rewriting:

    
    <?xml version="1.0" encoding="utf-8"?>
    
    <configuration>
    
      <system.webServer>
    
        <rewrite>
    
          <rules>
    
            <!-- Block/Redirect wp-admin to home -->
    
            <rule name="Block wp-admin for public" stopProcessing="true">
    
              <match url="^wp-admin(/.*)?$" ignoreCase="true" />
    
              <action type="Redirect" url="https://webtier.idealake.com/" redirectType="Found" />
    
            </rule>
    
            
    
            <!-- Reverse proxy all other requests to app tier -->
    
            <rule name="ReverseProxyToAppTier" stopProcessing="true">
    
              <match url="(.*)" />
    
              <conditions>
    
                <add input="{HTTP_HOST}" pattern="^webtier\.idealake\.com$" />
    
              </conditions>
    
              <action type="Rewrite" url="https://apptier.idealake.com/{R:1}" appendQueryString="true" logRewrittenUrl="true" />
    
              <serverVariables>
    
                <set name="HTTP_X_ORIGINAL_HOST" value="{HTTP_HOST}" />
    
                <set name="HTTP_X_FORWARDED_HOST" value="{HTTP_HOST}" />
    
                <set name="HTTP_X_FORWARDED_PROTO" value="https" />
    
              </serverVariables>
    
            </rule>
    
          </rules>
    
          
    
          <!-- Outbound rules to rewrite app tier URLs back to web tier -->
    
          <outboundRules rewriteBefore="true">
    
            <rule name="ReverseProxyOutboundRule" preCondition="IsHtmlResponse">
    
              <match filterByTags="A,Form,Img" pattern="^https?://apptier\.idealake\.com/(.*)" />
    
              <action type="Rewrite" value="https://webtier.idealake.com/{R:1}" />
    
            </rule>
    
            <rule name="ReverseProxyOutboundRule2" preCondition="IsHtmlResponse">
    
              <match filterByTags="A,Form,Img" pattern="^apptier\.idealake\.com/(.*)" />
    
              <action type="Rewrite" value="https://webtier.idealake.com/{R:1}" />
    
            </rule>
    
            <preConditions>
    
              <preCondition name="IsHtmlResponse">
    
                <add input="{RESPONSE_CONTENT_TYPE}" pattern="^text/html" />
    
              </preCondition>
    
            </preConditions>
    
          </outboundRules>
    
        </rewrite>
    
        <!-- Enable ARR proxy globally (add if missing) -->
    
        <httpErrors existingResponse="PassThrough" />
    
      </system.webServer>
    
    </configuration>
    
    
    • Key Changes:
      • Added conditions to match only webtier host.
      • Server variables forward original host/proto to app tier (prevents WordPress from using backend domain).
      • Outbound rules rewrite links/forms/images in HTML responses back to webtier (catches both http/https variants).
      • logRewrittenUrl="true" for debugging in Kudu logs.

    Save, then restart the web tier App Service (Overview > Restart).

    2. Update wp-config.php on App Tier

    Your current config is mostly good (HTTPS detection, WP_HOME/SITEURL set to webtier), but add host header preservation and disable canonical redirects to stop internal URL generation:

    
    <?php
    
    // ... existing code ...
    
    // Preserve original host from proxy
    
    if (isset($_SERVER['HTTP_X_FORWARDED_HOST'])) {
    
        $_SERVER['HTTP_HOST'] = $_SERVER['HTTP_X_FORWARDED_HOST'];
    
    }
    
    if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
    
        $_SERVER['HTTPS'] = 'on';
    
    }
    
    // Force all URLs to proxy domain
    
    define('WP_HOME', 'https://webtier.idealake.com');
    
    define('WP_SITEURL', 'https://webtier.idealake.com');
    
    define('WP_CONTENT_URL', 'https://webtier.idealake.com/wp-content');
    
    // Disable canonical redirects (prevents wp-admin/home leaks)
    
    remove_action('template_redirect', 'redirect_canonical');
    
    define('FORCE_SSL_ADMIN', true);
    
    // Relative paths for content (helps with proxy)
    
    define('DOMAIN_CURRENT_SITE', $_SERVER['HTTP_HOST']);
    
    // ... rest of your config ...
    
    
    • This ensures WordPress on app tier thinks it's running under webtier, generating correct URLs without redirects.

    3. Test and Debug

    • Clear Caches: On both tiers, clear WordPress cache (if using plugins like W3 Total Cache) and browser cache. Test incognito.
    • Verify Flow:
      • Homepage: webtier.idealake.com → Rewrites to apptier.idealake.com/ → Response rewritten back → Loads on webtier.
      • Other pages: e.g., webtier.idealake.com/about → Rewrites to apptier.idealake.com/about → No leak to apptier.
      • wp-admin: webtier.idealake.com/wp-admin → Redirects to webtier home (as per rule).
    • Logs Check: In Azure portal > Web Tier App Service > Log stream (real-time) or Diagnose and solve problems > Application Logs. Look for rewrite matches (e.g., "Rewritten URL: https://apptier...") or 200s with wrong Location headers.
      • Kudu Access: Site > Development Tools > Advanced Tools > Go > Debug console > LogFiles > Check for URL Rewrite logs.
    • Common Pitfalls:
      • If still 200 to apptier: Add <set name="HTTP_X_ORIGINAL_URL" value="{R:1}" /> in serverVariables for full path forwarding.
      • wp-admin access: For admin users, add IP whitelist or basic auth on web tier rule: <conditions><add input="{REMOTE_ADDR}" pattern="your-ip" /></conditions>.
      • HTTPS Only: Ensure both apps have "HTTPS Only" enabled (Configuration > General settings).

    4. Alternative: Use Azure Front Door for Robust Proxy

    If web.config tweaks are finicky (WordPress can override rewrites), migrate to Azure Front Door as a global reverse proxy:

    • Create Front Door > Backend pools: Add webtier as pool (no app tier exposure).
    • Routing rules: Forward * to webtier pool, override host header to apptier for backend.
    • Rules engine: Redirect wp-admin/* to /, rewrite others with host preservation.
    • Benefits: Handles SSL termination, caching, and WAF—more reliable than ARR for production WordPress.

    This setup works well for WordPress multi-tier (web for static/security, app for dynamic)—test incrementally (homepage first, then pages). If logs show specific errors (e.g., 502 on rewrite), share for fine-tuning.

    Best Regards,

    Jerald Felix

    Was this answer helpful?

    0 comments No comments

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.