Koszmar samouka: Jak pokonaliśmy błąd 403 z n8n, Google Cloud i Coolify

Self-hosting to obietnica ostatecznej wolności. Nie potrzebujesz Heroku, gdy masz Coolify. Możesz uruchomić własną, potężną platformę do automatyzacji z n8n, zamiast płacić za każde wykonanie. Kontrolujesz stos technologiczny, dane, cały swój wszechświat. To daje moc i jest efektywne kosztowo.

Jest wspaniale… dopóki nie przestaje być.

Oto historia jednego, doprowadzającego do szału błędu 403 Forbidden. Historia, która zaczyna się od prostego zadania — połączenia hostowanej instancji n8n z Google BigQuery — a kończy w najgłębszych warstwach sieci Dockera, zachowania proxy i ukrytych zabezpieczeń API Google. Jeśli kiedykolwiek czułeś tę miażdżącą duszę rozpacz błędu, który zdaje się przeczyć wszelkiej logice, ten artykuł jest dla Ciebie.

Scena: Proste zadanie, potężny stack

Nasz setup był piękny:

  • Serwer w chmurze zarządzany przez fantastyczną platformę PaaS open-source, Coolify.
  • Instancja n8n działająca w kontenerze Docker, zarządzana przez plik docker-compose.yml.
  • Kontener Traefik, również zarządzany przez Coolify, obsługujący reverse proxy i HTTPS.
  • Cel: Użyć node’a Google BigQuery w n8n, aby wykonać proste zapytanie SELECT 1.

Przy pierwszym uruchomieniu workflowu uderzył w nas niesławny błąd:

Pojawia się czarny charakter

403. That’s an error.

Your client does not have permission to get URL /bigquery/v2/projects/tantis-443509/jobs from this server. That’s all we know.

“Ach,” pomyśleliśmy naiwnie, “prosty problem z uprawnieniami.” Nie mogliśmy się bardziej mylić.

Droga przez mękę: Obieranie cebuli rozpaczy

Rozpoczęliśmy systematyczny, warstwa po warstwie, proces debugowania. Każdy krok obalał kolejną teorię i pogłębiał tajemnicę.

Hipoteza 1: Błędne poświadczenia lub role IAM

To oczywisty pierwszy krok. Sprawdziliśmy wszystko:

  • Typ poświadczeń w n8n (Service Account).
  • Poprawność wklejonego JSON-a konta serwisowego.
  • Role IAM w Google Cloud (BigQuery Job User, BigQuery Data Viewer, etc.).

Werdykt: Fałsz. Aby to udowodnić, połączyliśmy się przez SSH z serwerem hostującym, aktywowaliśmy dokładnie to samo konto serwisowe za pomocą gcloud CLI i wykonaliśmy bq query. Zadziałało idealnie. Poświadczenia i uprawnienia były bez zarzutu.

Hipoteza 2: Nieprawidłowe zakresy (Scopes)

Może generyczne poświadczenie Google API w n8n nie prosiło o właściwe zakresy OAuth? Ręcznie dodaliśmy https://www.googleapis.com/auth/bigquery do poświadczenia.

Werdykt: Fałsz. Błąd 403 pozostał bez zmian.

Hipoteza 3: Błąd w obsłudze poświadczeń przez n8n

To był punkt zwrotny. Skoro gcloud CLI potrafi wygenerować działający token, omińmy cały system n8n.

  1. Na hoście użyliśmy gcloud auth print-access-token, aby uzyskać znany, działający token typu “bearer”.
  2. W n8n użyliśmy standardowego node’a HTTP Request, wyłączyliśmy autoryzację i ręcznie dodaliśmy nagłówek Authorization: Bearer <token>.

Werdykt: Fałsz. Nawet z idealnym tokenem, żądanie z n8n wciąż kończyło się błędem 403. To było zdumiewające. Oznaczało, że problemem nie było poświadczenie ani token, ale coś w samym żądaniu opuszczającym kontener n8n.

Hipoteza 4: Blokowanie IP lub firewall

Komunikat błędu mówi “from this server”. Może publiczny IP kontenera n8n różni się od IP hosta i jest blokowany?

  • Na hoście: curl ifconfig.me -> 188.245.111.124
  • Wewnątrz kontenera: docker exec ... node -e "..." -> 188.245.111.124

Werdykt: Fałsz. Adres IP źródła był identyczny. To wykluczyło proste firewalle i VPC Service Controls oparte na IP.

Objawienie: Ostateczny test

Pozostała nam jedna możliwość: sama ścieżka sieciowa musi być inna. Zaprojektowaliśmy ostateczny test, aby to udowodnić. Stworzyliśmy prosty plik test_gcloud.js, który używał natywnego modułu https z Node.js do wykonania tego samego wywołania API, które próbował wykonać n8n.

  1. Test z hosta: Zainstalowaliśmy Node.js na serwerze hostującym i uruchomiliśmy node test_gcloud.js.
    • Wynik: STATUS_CODE: 200 (Sukces!)
  2. Test z kontenera: Skopiowaliśmy plik do działającego kontenera n8n (docker cp ...) i wykonaliśmy go (docker exec ... node /tmp/test_gcloud.js).
    • Wynik: STATUS_CODE: 403 (Porażka!)

To był dowód ostateczny. Ten sam kod, z tym samym tokenem, z tego samego źródłowego IP, działa z hosta, ale nie działa z kontenera.

Przyczyna stała się jasna: środowisko sieciowe Dockera. Ruch wychodzący z kontenera był transparentnie przepuszczany przez proxy, będące częścią warstwy sieciowej Coolify/Dockera. Serwer API Google widział to żądanie z proxy (prawdopodobnie przez metadane takie jak nagłówek X-Forwarded-For, który znaleźliśmy wcześniej), oznaczał je jako niezaufany przekaźnik ze względów bezpieczeństwa i odrzucał.

Rozwiązanie: Ominięcie proxy za pomocą sieci hosta

Jedynym sposobem na naprawę było sprawienie, by ruch sieciowy kontenera n8n był nieodróżnialny od ruchu z maszyny hostującej. Rozwiązaniem jest ominięcie abstrakcji sieciowej Dockera.

Poprawka to network_mode: 'host'.

Jednak wdrożenie tej zmiany to nie tylko jedna linijka. Ma ona kaskadowe konsekwencje dla połączeń z bazą danych i reverse proxy. Oto kompletne, działające rozwiązanie.

1. Zmodyfikuj swój docker-compose.yml

Musisz wprowadzić trzy kluczowe zmiany w usługach n8n i postgresql.

# W Twoim pliku docker-compose.yml dla n8n

services:
  n8n:
    image: docker.n8n.io/n8nio/n8n
    network_mode: 'host' # 1. Wymuś użycie stosu sieciowego hosta
    environment:
      # ... inne zmienne środowiskowe
      # 2. Zmień hosta bazy danych na adres loopback hosta
      - DB_POSTGRESDB_HOST=127.0.0.1
    volumes:
      - 'n8n-data:/home/node/.n8n'
    # 3. USUŃ wszystkie etykiety traefik. Nie zadziałają w trybie host.
    #    labels: ...

  postgresql:
    image: 'postgres:16-alpine'
    ports:
      # Udostępnij port bazy danych TYLKO dla interfejsu loopback hosta
      - "127.0.0.1:5432:5432"
    volumes:
      - 'postgresql-data:/var/lib/postgresql/data'
    environment:
      # ... zmienne środowiskowe postgresa

2. Ręcznie skonfiguruj Traefika

Ponieważ usunęliśmy etykiety Dockera, Traefik już nie wie, jak znaleźć n8n. Musisz zdefiniować trasę ręcznie, używając dynamicznego pliku konfiguracyjnego. Twój setup Coolify już nasłuchuje na zmiany w katalogu /data/coolify/proxy/dynamic/.

Stwórz nowy plik, np. /data/coolify/proxy/dynamic/n8n.yml:

# /data/coolify/proxy/dynamic/n8n.yml

http:
  routers:
    n8n-router:
      rule: "Host(`auto.critical.pl`)"
      service: n8n-service
      entryPoints:
        - "https" # Twój entrypoint nazywa się 'https', a nie 'websecure'
      tls:
        certResolver: "letsencrypt"

  services:
    n8n-service:
      loadBalancer:
        servers:
          # Użyj specjalnej nazwy DNS Dockera, która wskazuje na hosta
          - url: "http://host.docker.internal:5678"

Traefik zarządzany przez Coolify jest już skonfigurowany z extra_hosts do rozpoznawania host.docker.internal, więc to rozwiązanie zadziała idealnie.

3. Zrestartuj i świętuj

  1. Uruchom docker compose down w katalogu projektu n8n.
  2. Uruchom docker compose up -d, aby zastosować nową konfigurację.
  3. Zrestartuj Traefika dla pewności: docker restart coolify-proxy.

Po tym wszystkim, Twoja strona auto.critical.pl wróci do życia, a co ważniejsze, Twój node Google BigQuery zacznie działać poprawnie. Żądanie wychodzące będzie teraz pochodziło bezpośrednio ze stosu sieciowego hosta, omijając problematyczne proxy i prezentując “czyste” żądanie dla Google.

Wniosek: Cena wolności

Ta podróż jest potężnym przypomnieniem kreda każdego self-hostera. Wolność budowania własnego stacku wiąże się z odpowiedzialnością za zrozumienie każdej jego warstwy. Niejasny błąd 403 nie był prostym problemem z uprawnieniami; był głęboko zakorzenionym konfliktem między polityką bezpieczeństwa API Google a specyfiką sieciowego środowiska kontenerowego.

Więc następnym razem, gdy uderzysz w mur, który przeczy logice, nie przestawaj kopać. Problem może nie leżeć w aplikacji czy usłudze, ale w niewidzialnym kleju sieciowym, który je spaja.

Gotowy na strategiczne partnerstwo w marketingu cyfrowym?

Porozmawiajmy o Twoich wyzwaniach i celach. Opracujemy dopasowaną strategię i plan działania, które przyniosą mierzalne rezultaty dla Twojego e-commerce lub instytucji.