Table of Contents

    Book an Appointment

    How Do You Host a Laravel and Inertia Application in a Subdirectory Behind an Nginx Reverse Proxy?

    While working on a large-scale SaaS platform in the enterprise resource planning (ERP) sector, our team faced a common yet surprisingly nuanced infrastructure challenge. We had an expansive legacy application running on the root domain, and we were tasked with integrating a brand-new analytics and reporting module. To maintain a unified user experience, this new module needed to be served from a subdirectory, specifically at a path like /analytics.

    The new module was built using a modern stack: Laravel for the backend, Inertia.js for the SPA bridging, Vue.js for the interface, Vite for asset bundling, and Ziggy for frontend routing. At first glance, proxying traffic from our main Nginx API gateway to the internal application server seemed straightforward. However, as soon as the first staging deployment completed, we realized that while the backend received the requests, the frontend was fundamentally broken. Asset paths 404’d, pagination links generated without the subdirectory prefix, and Inertia state requests continuously redirected to the wrong endpoints.

    In production environments, seamless routing behind reverse proxies is critical. Misaligned base URLs not only break applications but can introduce security headers mismatch and CORS issues. This challenge inspired the following article so that other architects and technical leaders can avoid the technical debt of implementing hacky middleware overrides when deploying subdirectory applications.

    Where Did the Reverse Proxy Architecture Break Down in Our Enterprise System?

    The problem materialized at the intersection of Nginx, Laravel’s request lifecycle, and the frontend asset pipeline. Our Nginx configuration was set up to intercept traffic at the /analytics location block and proxy it to an upstream server. To prevent Laravel from trying to resolve the literal /analytics string in its routing table, Nginx was stripping the path using a trailing slash in the proxy_pass directive.

    Because Nginx stripped the prefix, Laravel genuinely believed it was serving traffic from the root domain. While we passed the standard proxy headers—such as X-Forwarded-Prefix, X-Forwarded-For, and X-Forwarded-Host—Laravel’s core routing and the associated frontend toolchain (Vite, Inertia, Ziggy) did not automatically adapt. The symptoms were immediate:

    • Ziggy generated frontend route helpers that pointed to the root domain instead of the subdirectory.
    • Vite injected asset URLs looking for CSS and JS bundles at the root, leading to blank screens.
    • Inertia.js components made XHR requests back to the server without the prefix, triggering 404 Not Found errors.

    What Caused Laravel, Vite, and Ziggy to Generate Incorrect URLs?

    The root cause was a disjointed understanding of the application’s base URL across the tech stack. Initially, to get things working, the team attempted to intercept the request lifecycle directly within the Inertia middleware. By adding a custom urlResolver inside the Inertia request handler, the application intercepted the X-Forwarded-Prefix header and manually rewrote the paths that Inertia thought it was visiting.

    Simultaneously, the proxy IP was added to Laravel’s TrustProxies middleware. While this duct-tape solution forced Inertia to recognize the subdirectory, it felt exceptionally brittle. It was a classic “hack” that didn’t solve the underlying problem: Laravel’s internal URL generator, which Ziggy relies heavily upon, was still unaware of the proxy prefix. Maintaining custom overrides in middleware creates technical debt, complicating future upgrades and making it harder for teams to scale the application. When companies hire software developer teams for enterprise projects, they expect robust architectural patterns, not localized patches.

    How Did We Evaluate and Approach the Reverse Proxy Routing Dilemma?

    Before arriving at the final production-ready implementation, we evaluated several architectural workarounds. It is crucial to examine the trade-offs of each to understand why framework-level configuration is superior to middleware manipulation.

    Did We Consider Hardcoding Route Prefixes in Laravel?

    Our first alternative was to use Laravel’s route grouping to wrap all web and API routes in a generic prefix. If we mapped all routes inside an /analytics group, we could change Nginx to pass the full URI instead of stripping it. However, this approach tightly couples the application code to the infrastructure deployment path. If the business later decided to move the application to a subdomain or change the subdirectory name, we would require a code change and a redeployment. This violated the principles of environment-independent application design.

    Why Not Use a Custom Inertia URL Resolver Middleware?

    The initial patch involved injecting a closure into the HandleInertiaRequests middleware to reconstruct the URL based on the X-Forwarded-Prefix. We considered keeping this approach since it isolated the fix to Inertia. However, this failed to address Ziggy’s route generation and Laravel’s native pagination links, mail notifications, and asset paths. Fixing only Inertia meant we would eventually need custom resolvers for every other system component, violating the DRY (Don’t Repeat Yourself) principle.

    Could We Modify the Nginx Proxy Pass Behavior Instead?

    We also analyzed whether Nginx could simply rewrite the request body and headers natively using sub_filter to rewrite URLs in the outbound HTML. While technically possible, this is computationally expensive and error-prone. A robust application must inherently know its base URL to generate secure, signed URLs and process OAuth redirects accurately.

    What is the Cleanest Technical Fix for Laravel Subdirectory Hosting?

    The correct implementation leverages Laravel’s native trust mechanisms and environment variables, ensuring every layer of the toolchain—from backend pagination to frontend Ziggy routing—synchronizes seamlessly without custom middleware overrides. This is the standard we apply when clients hire laravel developers for enterprise modernization projects.

    1. Configuring Nginx Correctly

    First, Nginx must forward the prefix and strip the location properly. The trailing slash on the proxy_pass is intentional, but passing the X-Forwarded-Prefix is what informs the application of its actual location.

    upstream analytics_app {
        server 10.0.1.50:8080;
        keepalive 16;
    }
    server {
        location ^~ /analytics/ {
            proxy_pass http://analytics_app/;
            
            proxy_set_header Host              $host;
            proxy_set_header X-Real-IP         $remote_addr;
            proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header X-Forwarded-Host  $host;
            proxy_set_header X-Forwarded-Port  $server_port;
            proxy_set_header X-Forwarded-Prefix /analytics;
            proxy_http_version 1.1;
            proxy_set_header Connection "";
        }
    }
    

    2. Configuring TrustProxies in Laravel

    Laravel must trust the Nginx server’s IP to accept the forwarded headers. In the TrustProxies.php middleware, we specify the internal IP ranges of our proxy layer.

    namespace AppHttpMiddleware;
    use IlluminateHttpMiddlewareTrustProxies as Middleware;
    use IlluminateHttpRequest;
    class TrustProxies extends Middleware
    {
        protected $proxies = [
            '10.0.0.0/8',
        ];
        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_PREFIX;
    }
    

    3. Enforcing the Root URL Globally

    Instead of hacking Inertia, we instruct Laravel’s internal URL generator to respect the forwarded prefix globally. We do this in the AppServiceProvider.php boot method. This ensures that Ziggy, standard routing, and redirects naturally inherit the correct base path.

    namespace AppProviders;
    use IlluminateSupportServiceProvider;
    use IlluminateSupportFacadesURL;
    use IlluminateHttpRequest;
    class AppServiceProvider extends ServiceProvider
    {
        public function boot(Request $request): void
        {
            if ($request->hasHeader('X-Forwarded-Prefix')) {
                $prefix = rtrim($request->header('X-Forwarded-Prefix'), '/');
                URL::forceRootUrl($request->root() . $prefix);
            }
            
            if ($this->app->environment('production')) {
                URL::forceScheme('https');
            }
        }
    }
    

    4. Aligning Vite and Environment Variables

    Finally, the frontend asset compilation must be explicitly instructed on where to locate files. In the .env file, we set standard variables:

    APP_URL=https://my-domain.com/analytics
    ASSET_URL=https://my-domain.com/analytics
    

    In vite.config.js, we apply the base path so that generated manifests point to the correct subdirectory:

    import { defineConfig, loadEnv } from 'vite';
    import laravel from 'laravel-vite-plugin';
    import vue from '@vitejs/plugin-vue';
    export default defineConfig(({ mode }) => {
        const env = loadEnv(mode, process.cwd(), '');
        return {
            base: env.ASSET_URL || '/',
            plugins: [
                laravel({
                    input: 'resources/js/app.js',
                    refresh: true,
                }),
                vue({
                    template: {
                        transformAssetUrls: {
                            base: null,
                            includeAbsolute: false,
                        },
                    },
                }),
            ],
        };
    });
    

    What Architectural Lessons Can Engineering Teams Learn from Subdirectory Proxying?

    Navigating reverse proxy configurations reveals several important best practices for engineering teams managing complex deployments:

    • Trust the Framework Native Tools: Always rely on core URL generators and service providers rather than injecting ad-hoc closures in middleware.
    • Decouple Infrastructure from Code: Never hardcode routing prefixes in your application codebase. Use standard HTTP headers like X-Forwarded-Prefix to dynamically inform the application of its environment.
    • Ensure Full-Stack Alignment: Fixing backend routing is only half the battle. You must ensure your frontend build tools (Vite, Webpack) and client-side routers (Ziggy, Vue Router) read from the same environment configuration.
    • Enforce HTTPS at the Provider Level: When sitting behind a proxy that handles SSL termination, explicitly force the HTTPS scheme in the application layer to prevent mixed-content errors.
    • Hire Experts for Edge Cases: Configuration drifts in proxy layers can lead to severe security and operational risks. When organizations hire frontend developers to resolve proxy routing alongside backend engineers, having a unified architectural vision is paramount to success.

    How Can We Help Your Team Tackle Complex Application Deployments?

    Deploying interconnected modern frameworks into legacy infrastructure requires more than just reading documentation; it requires hands-on experience and a deep understanding of full-stack data flow. By removing fragile middleware overrides and relying on Laravel’s core proxy trust architecture, we delivered a robust, scalable analytics module that seamlessly integrated with our client’s primary domain.

    If your organization is struggling with complex infrastructure integrations, application modernization, or architectural bottlenecks, we can provide the proven technical leadership you need. Whether you need to scale your existing platform or hire software developer teams with deep expertise in modern enterprise stacks, we are ready to assist. Reach out and contact us to discuss your next technical milestone.

    Social Hashtags

    #Laravel #Nginx #InertiaJS #VueJS #Vite #Ziggy #PHP #WebDevelopment #DevOps #ReverseProxy #LaravelDevelopment #SoftwareArchitecture #FullStackDevelopment #SaaS #WebPerformance

     

    Frequently Asked Questions

    Success Stories That Inspire

    See how our team takes complex business challenges and turns them into powerful, scalable digital solutions. From custom software and web applications to automation, integrations, and cloud-ready systems, each project reflects our commitment to innovation, performance, and long-term value.