Mehrere Shop-Installationen unter derselben Domain mit NGINX

Hallo zusammen!

Ich möchte unter einer Domain mehrere Shops installieren. Die Idee ist, dass es für verschiedene Länder jeweils eigene und unabhängige Shops gibt, z.B. shopdomain .com/de shopdomain .com/en etc.

Der Webserver läuft mit NGINX.

Ich habe Shopware 6.5.5.1 zu Testzwecken zweimal installiert, jeweils in ein eigenes Verzeichnis:

  1. /var/www/html/developer/dev-test
  2. /var/www/html/developer/dev-app1

In der NGINX conf läuft die erste Installation auf der Domain ohne Unterverzeichnis, also shopdomain .com → läuft einwandfrei, inkl. Backend.

Für die zweite Installation habe ich einen proxy eingerichtet. Nach dem gleichen Schema sollen weitere Installationen hinzukommen.

Meine NGINX conf sieht wie folgt aus:

server {

    index index.php index.html;
    server_name gerwin-gesundheitsprodukte.com;

    client_max_body_size 128M;

    location ^~ /app1 {
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-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 https;
        proxy_pass http://127.0.0.1:81;
        proxy_redirect off;
    }

    root /var/www/html/developer/dev-test/public;

    # Shopware update
    location /recovery/update/ {
        location /recovery/update/assets {
        }
        if (!-e $request_filename){
            rewrite . /recovery/update/index.php last;
        }
    }

    # new for Shopware 6.5
	location /shopware-installer.phar.php {
	    try_files $uri /shopware-installer.phar.php$is_args$args;
	}

    location ~* ^.+\.(?:css|cur|js|jpe?g|gif|ico|png|svg|webp|html|woff|woff2|xml)$ {
        expires 1y;
        add_header Cache-Control "public, must-revalidate, proxy-revalidate";

        access_log off;

        # The directive enables or disables messages in error_log about files not found on disk.
        log_not_found off;

        tcp_nodelay off;

        ## Set the OS file cache.
        open_file_cache max=3000 inactive=120s;
        open_file_cache_valid 45s;
        open_file_cache_min_uses 2;
        open_file_cache_errors off;

        try_files $uri /index.php$is_args$args;
    }

    location ~* ^.+\.svg$ {
        add_header Content-Security-Policy "script-src 'none'";
    }

    location / {
        try_files $uri /index.php$is_args$args;
    }

    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        include fastcgi.conf;
        fastcgi_param HTTP_PROXY "";
        fastcgi_buffers 8 16k;
        fastcgi_buffer_size 32k;
        proxy_connect_timeout 300s;
        proxy_send_timeout 300s;
        proxy_read_timeout 300s;
        send_timeout 300s; 
        client_body_buffer_size 128k;
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
        http2_push_preload on;
    }

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/gerwin-gesundheitsprodukte.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/gerwin-gesundheitsprodukte.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}

server {
    if ($host = gerwin-gesundheitsprodukte.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    listen 80;
    server_name gerwin-gesundheitsprodukte.com;
    return 404; # managed by Certbot


}

server {
    # Virtual host for web application 1:
    # - Only local (127.0.0.1), cannot be addressed directly (without gateway host).
    # - Listens on port 81 (HTTP) - IPv4 and IPv6.
    # - Is capable of serving requests for local IP address 127.0.0.1.

    listen 81;
    server_name 127.0.0.1;

    set $subdir_root /var/www/html/developer/dev-app1/public;
    root $subdir_root;

    rewrite ^/app1(.*)$ /$1 break;
    sub_filter "https://gerwin-gesundheitsprodukte.com/" "https://gerwin-gesundheitsprodukte.com/app1/";
    sub_filter_once off;


    # Shopware update
    location /recovery/update/ {
        location /recovery/update/assets {
        }
        if (!-e $request_filename){
            rewrite . /recovery/update/index.php last;
        }
    }

    # new for Shopware 6.5
    location /shopware-installer.phar.php {
        try_files $uri /shopware-installer.phar.php$is_args$args;
    }

    location ~* ^.+\.(?:css|cur|js|jpe?g|gif|ico|png|svg|webp|html|woff|woff2|xml)$ {
        expires 1y;
        add_header Cache-Control "public, must-revalidate, proxy-revalidate";

        access_log off;

        # The directive enables or disables messages in error_log about files not found on disk.
        log_not_found off;

        tcp_nodelay off;

        ## Set the OS file cache.
        open_file_cache max=3000 inactive=120s;
        open_file_cache_valid 45s;
        open_file_cache_min_uses 2;
        open_file_cache_errors off;

        try_files $uri /index.php$is_args$args;
    }

    location ~* ^.+\.svg$ {
        add_header Content-Security-Policy "script-src 'none'";
    }

    location / {
        try_files $uri /index.php$is_args$args;
    }

    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        include fastcgi.conf;
        fastcgi_param SCRIPT_FILENAME $request_filename;
        fastcgi_param DOCUMENT_ROOT $subdir_root;
        fastcgi_param HTTPS on;
        fastcgi_param HTTP_PROXY "";
        fastcgi_buffers 8 16k;
        fastcgi_buffer_size 32k;
        proxy_connect_timeout 300s;
        proxy_send_timeout 300s;
        proxy_read_timeout 300s;
        send_timeout 300s;
        client_body_buffer_size 128k;
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
        http2_push_preload on;
    }

}

Auch der zweite Shop lässt sich aufrufen. Aber ich kann das Backend nicht öffnen. Es erscheint folgende Fehlermeldung:

Shopware\Administration\Controller\AdministrationController::index(): Argument #2 ($context) must be of type Shopware\Core\Framework\Context, null given, called in /var/www/html/developer/dev-app1/vendor/symfony/http-kernel/HttpKernel.php on line 182

Ich sehe grad den Wald vor lauter Bäumen nicht. Hat jemand einen Hinweis, was an meiner NGINX Konfiguration vielleicht nicht stimmt?

Gruß,
Dorina

Einen workaround habe ich gefunden:

Ich greife auf das Backend über eine subdomain zu:

server {

    index index.php index.html;
    server_name en.gerwin-gesundheitsprodukte.com;

    client_max_body_size 128M;

    root /var/www/html/developer/dev-app1/public;
#rest of config
}

Ist aber sicher keine Lösung für den produktiven Betrieb, denn in der .env Datei ist ja eine andere Domain als Shop-Adresse eingetragen. Aber es zeigt einmal mehr, dass die server config nicht korrekt ist.

Die eigentliche „Magie“ passiert ja in diesem Block:

        location ^~ /app1/ {
                proxy_set_header Accept-Encoding "";
                proxy_set_header Host $host;
                proxy_set_header X-Forwarded-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 https;
                proxy_pass_request_headers on;
                proxy_pass http://127.0.0.1:81;
                proxy_redirect off;
        }

und dann:

server {
        listen 81;
        server_name 127.0.0.1;

        set $subdir_root /var/www/html/developer/dev-app1/public;
        root $subdir_root;

        rewrite ^/app1(.*)$ /$1 break;
        sub_filter_once off;
        sub_filter "https://gerwin-gesundheitsprodukte.com/" "https://gerwin-gesundheitsprodukte.com/app1/";
#rest is standard
}

Im ersten Block setze ich für den URI Teil /app1/ einen reverse proxy auf localhost fest auf dem port 81. Es folgen eine settings; hier könnte etwas fehlen oder falsch konfiguriert sein.

Der zweite Block schreibt die URI um:

rewrite ^/app1(.*)$ /$1 break;

sonst kommt ein request etwa wie: /var/www/html/developer/dev-app1/public/app1 erwartet wäre aber /var/www/html/developer/dev-app1/public → NGINX hängt also die URI /app1 hinten an.

Dann aber werden css, js etc Dateien gesucht unter der root URL. Deswegen wird diese mit sub_filter umgeschrieben:

        sub_filter_once off;
        sub_filter "https://gerwin-gesundheitsprodukte.com/" "https://gerwin-gesundheitsprodukte.com/app1/";

Der Storefront ist damit erreichbar. Dennoch wird irgendetwas nicht mitgeliefert, denn die HttpKernel.php bekommt offensichtlich keinen $context geliefert.

Sieht jemand, wo ich einen Denkfehler habe?

Hallo Dorina,

vielleicht kann ich dir ein wenig weiterhelfen oder zumindest einen Schubs in die richtige Richtung geben.

Grundsätzlich kann eine solche Installation in dem bei uns eingesetzten ISPConfig und mit unseren Vorlagen mit ein paar Klicks bewerkstelligt werden.

Es gibt aber natürlich auch einen manuellen Weg, denn ich gerne einmal grob skizziere:

  • Gemeinsamer Stammordner für alle Shops: /var/www/meinedomain.de/
  • Unterordner für /de-Shop: /var/www/meinedomain.de/de/
  • Unterordner für /en/-Shop: /var/www/meinedomain.de/en/

nginx-Direktiven für den gemeinsamen vhost der Domain meinedomain.de:

server {
    listen 1.2.3.4:80;
    listen 1.2.3.4:443 ssl;
    http2 on;
    ssl_protocols TLSv1.3;
    ssl_certificate /pfad/zum/ssl/zertifikat;
    ssl_certificate_key /pfad/zum/ssl/key;
    server_name meinedomain.de www.meinedomain.de;
    root   /var/www/meinedomain.de/web;

    error_log /var/log/nginx/meinedomain.de/error.log;
    access_log /var/log/nginx/meinedomain.de/access.log;

    index index.html index.htm index.php index.cgi index.pl index.xhtml;

    location ~ /\.(?!well-known/) {
        deny all;
        access_log off;
        log_not_found off;
    }

    location = /favicon.ico {
        log_not_found off;
        access_log off;
    }

    location = /robots.txt {
        allow all;
        log_not_found off;
        access_log off;
    }

    # Hier einen Redirect für den "Haupt" Shop einstellen, wenn nur die Domain aufgerufen wird
    rewrite ^/$ /de/ permanent;

# Beginn Regeln für /de

    rewrite ^/de$ /de/ permanent;

    location @swupdateDE {
        root /var/www/meinedomain.de/web/de/public/;
        client_max_body_size 128M;
        include /etc/nginx/fastcgi_params;
        fastcgi_split_path_info ^/de/(.+\.php)(.*)$;
        fastcgi_param SCRIPT_NAME /de/$fastcgi_script_name;
        fastcgi_param SCRIPT_FILENAME $realpath_root/$fastcgi_script_name;
        fastcgi_param PATH_INFO       $fastcgi_path_info;
        fastcgi_param PHP_SELF       /de/$fastcgi_script_name;
        fastcgi_param HTTP_PROXY "";
        fastcgi_buffers 8 16k;
        fastcgi_buffer_size 32k;
        fastcgi_connect_timeout 300s;
        fastcgi_send_timeout 300s;
        fastcgi_read_timeout 300s;
        send_timeout 300s;
        client_body_buffer_size 128k;
        fastcgi_pass unix:/pfad/zum/php/socket;
    }

    location @rewriteappDE {
        root /var/www/meinedomain.de/web/de/public/;
        client_max_body_size 128M;
        rewrite ^/de/(.*)$ /de/index.php last;
    }

    location /de/ {
        index index.php index.html;
        client_max_body_size 128M;
        alias /var/www/meinedomain.de/web/de/public/;

        try_files $uri @rewriteappDE;

        location /de/recovery/update/ {
            location /de/recovery/update/assets {
            }
            try_files $uri /de/recovery/update/index.php;
        }

        location ~ ^/de/shopware-installer\.phar\.php(/|$) {
            alias /var/www/meinedomain.de/web/;
            include /etc/nginx/fastcgi_params;
            fastcgi_split_path_info ^/de/(.+\.php)(.*)$;

            set $path_info $fastcgi_path_info;

            fastcgi_param PATH_INFO $path_info;
            fastcgi_param SCRIPT_NAME /de/$fastcgi_script_name;
            fastcgi_param SCRIPT_FILENAME $realpath_root/de/$fastcgi_script_name;
            fastcgi_param PHP_SELF       /de/$fastcgi_script_name;
            fastcgi_param HTTP_PROXY "";

            try_files /de/$fastcgi_script_name @swupdateDE;

            fastcgi_buffers 8 16k;
            fastcgi_buffer_size 32k;
            fastcgi_connect_timeout 300s;
            fastcgi_send_timeout 300s;
            fastcgi_read_timeout 300s;
            send_timeout 300s;
            client_body_buffer_size 128k;
            fastcgi_pass unix:/pfad/zum/php/socket;
        }

        location ~ ^/de/(.*)\.php(/|$) {
            include /etc/nginx/fastcgi_params;
            fastcgi_split_path_info ^/de/(.+\.php)(.*)$;
            fastcgi_param SCRIPT_NAME /de/$fastcgi_script_name;
            fastcgi_param SCRIPT_FILENAME $realpath_root/$fastcgi_script_name;
            fastcgi_param PATH_INFO       $fastcgi_path_info;
            fastcgi_param PHP_SELF       /de/$fastcgi_script_name;
            fastcgi_param HTTP_PROXY "";
            fastcgi_buffers 8 16k;
            fastcgi_buffer_size 32k;
            fastcgi_connect_timeout 300s;
            fastcgi_send_timeout 300s;
            fastcgi_read_timeout 300s;
            send_timeout 300s;
            client_body_buffer_size 128k;
            fastcgi_pass unix:/pfad/zum/php/socket;
        }

        location ~* ^/de/.+\.(?:css|cur|js|jpe?g|gif|ico|png|webp|html|woff|woff2|xml)$ {
            expires 1y;
            add_header Cache-Control "public, must-revalidate, proxy-revalidate";

            access_log off;

            log_not_found off;

            tcp_nodelay off;

            open_file_cache max=3000 inactive=120s;
            open_file_cache_valid 45s;
            open_file_cache_min_uses 2;
            open_file_cache_errors off;

            try_files $uri @rewriteappDE;
        }

        location ~* ^/de/.+\.svg$ {
            expires 1y;
            add_header Cache-Control "public, must-revalidate, proxy-revalidate";
            add_header Content-Security-Policy "script-src 'none'";

            access_log off;

            log_not_found off;

            tcp_nodelay off;

            open_file_cache max=3000 inactive=120s;
            open_file_cache_valid 45s;
            open_file_cache_min_uses 2;
            open_file_cache_errors off;

            try_files $uri @rewriteappDE;
        }
    }


# Beginn Regeln für /en
    rewrite ^/en$ /en/ permanent;

    location @swupdateEN {
        root /var/www/meinedomain.de/web/en/public/;
        client_max_body_size 128M;
        include /etc/nginx/fastcgi_params;
        fastcgi_split_path_info ^/en/(.+\.php)(.*)$;
        fastcgi_param SCRIPT_NAME /en/$fastcgi_script_name;
        fastcgi_param SCRIPT_FILENAME $realpath_root/$fastcgi_script_name;
        fastcgi_param PATH_INFO       $fastcgi_path_info;
        fastcgi_param PHP_SELF       /en/$fastcgi_script_name;
        fastcgi_param HTTP_PROXY "";
        fastcgi_buffers 8 16k;
        fastcgi_buffer_size 32k;
        fastcgi_connect_timeout 300s;
        fastcgi_send_timeout 300s;
        fastcgi_read_timeout 300s;
        send_timeout 300s;
        client_body_buffer_size 128k;
        fastcgi_pass unix:/pfad/zum/php/socket;
    }

    location @rewriteappEN {
        root /var/www/meinedomain.de/web/en/public/;
        client_max_body_size 128M;
        rewrite ^/en/(.*)$ /en/index.php last;
    }

    location /en/ {
        index index.php index.html;
        client_max_body_size 128M;
        alias /var/www/meinedomain.de/web/en/public/;

        try_files $uri @rewriteappEN;

        location /en/recovery/update/ {
            location /en/recovery/update/assets {
            }
            try_files $uri /en/recovery/update/index.php;
        }

        location ~ ^/en/shopware-installer\.phar\.php(/|$) {
            alias /var/www/meinedomain.de/web/;
            include /etc/nginx/fastcgi_params;
            fastcgi_split_path_info ^/en/(.+\.php)(.*)$;

            set $path_info $fastcgi_path_info;

            fastcgi_param PATH_INFO $path_info;
            fastcgi_param SCRIPT_NAME /en/$fastcgi_script_name;
            fastcgi_param SCRIPT_FILENAME $realpath_root/en/$fastcgi_script_name;
            fastcgi_param PHP_SELF       /en/$fastcgi_script_name;
            fastcgi_param HTTP_PROXY "";

            try_files /en/$fastcgi_script_name @swupdateEN;

            fastcgi_buffers 8 16k;
            fastcgi_buffer_size 32k;
            fastcgi_connect_timeout 300s;
            fastcgi_send_timeout 300s;
            fastcgi_read_timeout 300s;
            send_timeout 300s;
            client_body_buffer_size 128k;
            fastcgi_pass unix:/pfad/zum/php/socket;
        }

        location ~ ^/en/(.*)\.php(/|$) {
            include /etc/nginx/fastcgi_params;
            fastcgi_split_path_info ^/en/(.+\.php)(.*)$;
            fastcgi_param SCRIPT_NAME /en/$fastcgi_script_name;
            fastcgi_param SCRIPT_FILENAME $realpath_root/$fastcgi_script_name;
            fastcgi_param PATH_INFO       $fastcgi_path_info;
            fastcgi_param PHP_SELF       /en/$fastcgi_script_name;
            fastcgi_param HTTP_PROXY "";
            fastcgi_buffers 8 16k;
            fastcgi_buffer_size 32k;
            fastcgi_connect_timeout 300s;
            fastcgi_send_timeout 300s;
            fastcgi_read_timeout 300s;
            send_timeout 300s;
            client_body_buffer_size 128k;
            fastcgi_pass unix:/pfad/zum/php/socket;
        }

        location ~* ^/en/.+\.(?:css|cur|js|jpe?g|gif|ico|png|webp|html|woff|woff2|xml)$ {
            expires 1y;
            add_header Cache-Control "public, must-revalidate, proxy-revalidate";

            access_log off;

            log_not_found off;

            tcp_nodelay off;

            open_file_cache max=3000 inactive=120s;
            open_file_cache_valid 45s;
            open_file_cache_min_uses 2;
            open_file_cache_errors off;

            try_files $uri @rewriteappEN;
        }

        location ~* ^/en/.+\.svg$ {
            expires 1y;
            add_header Cache-Control "public, must-revalidate, proxy-revalidate";
            add_header Content-Security-Policy "script-src 'none'";

            access_log off;

            log_not_found off;

            tcp_nodelay off;

            open_file_cache max=3000 inactive=120s;
            open_file_cache_valid 45s;
            open_file_cache_min_uses 2;
            open_file_cache_errors off;

            try_files $uri @rewriteappEN;
        }
    }
}

Ich hoffe, das hilft dir ein wenig weiter.

Falls du das mal bei uns testen möchtest:
Mit unseren Testpaketen geht das 14 Tage lang kostenlos :slight_smile:

1 „Gefällt mir“