Jak zoptymalizować Nginx - icomMedia

Jak zoptymalizować Nginx

Jak zoptymalizować Nginx

Optymalizacja Nginx to nie jednorazowy trik, ale ciągły proces podejmowania decyzji, które równoważą stabilność, łatwość utrzymania i realne korzyści biznesowe. W praktyce zaczyna się od zrozumienia, jak serwer obsługuje gniazda, jak zarządza pamięcią, jak buforuje dane i jak współpracuje z systemem operacyjnym. To także umiejętność czytania metryk i logów, porównywania efektów różnych ustawień oraz odcinania złudzeń: nie każde „tunowanie” przyniesie mierzalny zysk. Celem tego przewodnika jest przejście przez najważniejsze obszary konfiguracji Nginx, zestawiając zasady, dobre praktyki i konkretne przykłady. Dzięki temu łatwiej wybrać właściwe kroki dla danego obciążenia: małego API, serwowania plików statycznych, dużego ruchu za reverse proxy, czy intensywnego szyfrowania ruchu.

Dlaczego i co optymalizować w Nginx

Nginx jest asynchronicznym serwerem opartym o pętlę zdarzeń i nieblokujące operacje I/O. To sprawia, że znakomicie skaluje się w zadaniach sieciowych, o ile odpowiednio dobierzesz liczbę procesów roboczych, rozmiary buforów i model zdarzeń. Zanim jednak dotkniesz konfiguracji, wyznacz cele: czy chodzi o większą wydajność przy tym samym sprzęcie, niższą latencja dla API, lepszą przepustowość dla plików statycznych, czy np. niższe zużycie CPU podczas negocjacji TLS? W każdym z tych scenariuszy inny zestaw dźwigni będzie najistotniejszy.

Źródła opóźnień można rozbić na kilka warstw: transport (TCP, buforowanie pakietów, zrywanie połączeń), aplikacja (czas odpowiedzi upstream), dysk (dostęp do plików, cache systemowy), kryptografia (negocjacja i szyfrowanie), a także mechanizmy samego Nginx (bufory, kolejki, harmonogram zdarzeń). Optymalizacja rzadko polega na przestawieniu jednego „magicznego” przełącznika. Zwykle to suma kilku zmian, które w połączeniu efektywnie skracają ścieżkę żądania.

Zasada numer jeden: mierz. Zanim dokonasz zmiany, zapisz punkt odniesienia, najlepiej zestaw kilku metryk: czas TTFB, 95./99. percentyl czasu odpowiedzi, liczba aktywnych połączeń, prędkość wysyłania bajtów, wykorzystanie CPU i pamięci, ewentualnie statystyki dysku. Po zmianie – powtórz test. Zadbaj o powtarzalność: identyczny ruch testowy, podobny rozkład zapytań, zbliżone zestawy odpowiedzi (rozmiar i typ). Testy syntetyczne (np. wrk, h2load, k6) są świetnym początkiem, ale poza nimi śledź metryki w środowisku produkcyjnym, bo to tam wychodzą na jaw niuanse.

Podstawowe parametry procesu i zdarzeń

Większość środowisk dobrze działa z automatycznym doborem liczby procesów roboczych:

  • worker_processes auto;
  • worker_cpu_affinity – tylko jeśli dokładnie wiesz, co robisz (przypinanie do rdzeni może pomóc przy bardzo przewidywalnym ruchu, ale ograniczać elastyczność w środowiskach współdzielonych).

Każdy worker ma własną pętlę zdarzeń. Na Linuksie domyślnie używany jest epoll (events { use epoll; }). Kluczowy jest limit jednoczesnych połączeń:

  • events { worker_connections 8192; multi_accept on; }

Wartość worker_connections określa liczbę deskryptorów, którymi worker może zarządzać. Oprócz połączeń klienckich są to również połączenia do upstreamów, pliki, gniazda logów. Często bezpieczniej jest iść nieco wyżej (np. 16384), o ile OS i pamięć na to pozwalają. Pamiętaj o systemowych limitach liczby otwartych plików:

  • worker_rlimit_nofile 200000;
  • fs.file-max (sysctl) oraz limity w systemd/ulimit.

Wysokie obciążenie akceptacją nowych połączeń warto rozproszyć po rdzeniach:

  • listen … reuseport; – umożliwia kernelowi równoważenie akceptacji pomiędzy workerami;
  • accept_mutex off; – w połączeniu z reuseport zwykle daje lepszą równoległość.

Bufory i nagłówki warto dopasować do realiów ruchu. Jeżeli spodziewasz się bardzo dużych nagłówków (np. długie ciasteczka), podnieś:

  • client_header_buffer_size 4k/8k;
  • large_client_header_buffers 4 16k;

Zbyt niskie wartości skutkują 400 Bad Request lub wieloma rezerwacjami pamięci. Zbyt wysokie – niepotrzebnym footprintem. Traktuj to jako suwak, który dostrajasz na podstawie logów błędów i profilu ruchu.

Statyczne pliki i sieć: szybka warstwa transportowa

Serwowanie plików statycznych to domena Nginx. By wycisnąć maksimum, zacznij od I/O:

  • sendfile on; – pozwala kernelowi przesyłać dane bez kopiowania ich przez przestrzeń użytkownika;
  • tcp_nopush on; – porządkuje nagłówki i payload, optymalizując wysyłkę dużych bloków;
  • tcp_nodelay on; – redukuje opóźnienia dla mniejszych odpowiedzi, zwłaszcza w keep-alive;
  • aep: dla bardzo dużych plików i wolnego storage rozważ AIO: aio on; lub aio threads; oraz directio dla dużych rozmiarów (np. directio 4m;).

Plikowy cache deskryptorów i metadanych znacząco zmniejsza koszt statystyk plików:

  • open_file_cache max=200000 inactive=60s;
  • open_file_cache_valid 30s;
  • open_file_cache_min_uses 2;
  • open_file_cache_errors on;

Warto, by Nginx mógł odpowiadać z pełnymi informacjami o wersji zasobu:

  • etag on; – pomaga klientom i CDN w weryfikacji zasobów;
  • if_modified_since exact; – pozwala efektywnie odsyłać 304 Not Modified;
  • expires i cache-control – ustaw rozsądne nagłówki dla assetów (np. długie dla fingerprintowanych plików).

Redukcja objętości danych jest kluczowa, dlatego stosuj kompresja świadomie. gzip jest powszechny i tani CPU-wo, brotli daje zwykle lepszy współczynnik, ale bywa cięższy i wymaga modułu. Przykład zbalansowanej konfiguracji:

  • gzip on; gzip_comp_level 4; gzip_min_length 1024; gzip_types text/plain text/css application/javascript application/json image/svg+xml;
  • gzip_static on; – jeśli budujesz artefakty .gz podczas CI/CD;
  • brotli on; brotli_comp_level 4; brotli_types jak wyżej – jeśli masz moduł i CPU na to pozwala.

Nie kompresuj obrazów czy binariów – strata CPU bez zysku. Pamiętaj, że kompresja „gryzie się” z TLS tylko o tyle, że rośnie koszt CPU. Zadbaj o rozmiary buforów odpowiedzi i wysyłki:

  • output_buffers i sendfile_max_chunk – zwykle domyślne są OK, ale przy bardzo dużych plikach warto testować mniejsze kawałki, by nie blokować workerów przy wolnych klientach;
  • send_timeout 15s–60s w zależności od profilu klientów.

Reverse proxy i upstreamy

Jako reverse proxy Nginx pośredniczy między klientem a aplikacją. Najważniejsze decyzje dotyczą połączeń do zaplecza, buforowania odpowiedzi i przepływu danych. Ustal wersję protokołu do upstreamu (HTTP/1.1 zalecany dla keep-alive), kontroluj kolejki oraz limity. Dobrze ustawiona pula połączeń w górę minimalizuje koszty TCP i ogranicza stan TIME_WAIT.

Podstawy:

  • proxy_http_version 1.1; oraz proxy_set_header Connection „”;
  • upstream backend { server 10.0.0.10:8080; server 10.0.0.11:8080 max_fails=3 fail_timeout=10s; keepalive 128; } – włącza po stronie Nginx trwałe połączenia do zaplecza;
  • proxy_connect_timeout 3s; proxy_send_timeout 30s; proxy_read_timeout 30s; – dostosuj do SLA aplikacji;
  • proxy_buffering on; proxy_buffers 32 16k; proxy_busy_buffers_size 64k; proxy_buffer_size 16k; – buforowanie ogranicza backpressure przy wolnym kliencie;
  • proxy_max_temp_file_size 0; – jeśli nie chcesz zapisu na dysk przy bardzo dużych odpowiedziach (ostrożnie);
  • proxy_request_buffering on/off – off bywa lepsze dla strumieni (np. upload), ale zwiększa presję na aplikację.

Równoważenie ruchu: domyślny round-robin zwykle wystarcza, ale wrażliwym na opóźnienia usługom pomaga least_conn; dla API o dużej zmienności rozmiaru odpowiedzi – random two; dla sticky sessions rozważ sticky cookie albo consistent hashing. Zawsze łącz to z aktywnym health-checkingiem (moduł commercial lub pasywnie: proxy_next_upstream error timeout http_500 http_502 http_503 http_504;).

Trwałe połączenia keepalive do zaplecza są jednym z największych „darmowych” zysków – eliminują ciągłe trzystronne uściski i powtarzalny koszt TLS, jeśli stosujesz TLS na łączu do aplikacji. Pamiętaj jednak o granicach: keepalive_requests 1000–5000 ogranicza liczbę żądań na jedno połączenie, co pomaga uciec przed wyciekami pamięci po stronie aplikacji. Ustal też proxy_next_upstream_timeout i proxy_next_upstream_tries, by zapobiegać lawinom retry przy degradacji jednego z serwerów.

Na interfejsie publicznym nie przekombinuj. Niektóre ustawienia, które „brzmią szybko”, w praktyce pogarszają sytuację w obliczu nieregularnych klientów mobilnych. Mierz takie zmiany jak ograniczanie buforów klienta (client_body_buffer_size), skracanie keepalive_timeout (zbyt krótki – częste renegocjacje) czy agresywne cięcie timeouts (zbyt krótkie – niepotrzebne retry i błędy).

Jeśli Twoje zaplecze wspiera HTTP/2, możesz rozważyć HTTP/2 do upstreamu przez proxy uwzględniając natywne moduły lub zewnętrzne rozszerzenia, ale na dziś stabilniejszym i bardziej przewidywalnym wyborem jest HTTP/1.1 z utrzymywaniem połączeń. O funkcjach takich jak request mirroring (mirror) czy subrequests myśl ostrożnie – wygodne do telemetrii A/B, ale każdy dodatkowy request to realny koszt.

W miejscach łączenia z kilkoma upstreamami o różnym profilu odpowiedzi przydają się mapy i warunki: np. warunkowe wyłączanie buforowania przy długich strumieniach SSE/WebSocket, regulacja proxy_buffering off; dla location z Upgrade: websocket, lub inne ścieżki. To wszystko pozwala spiąć w jednym Nginx różne klasy ruchu bez wzajemnego „przeciekania” opóźnień.

Cache po stronie Nginx: mikrocache i koherencja

Wbudowany cache Nginx potrafi dramatycznie obniżyć obciążenie aplikacji, jeśli odpowiedzi są choć trochę powtarzalne. Zwykłe 1–5 sekund tzw. mikrocache na dynamicznych endpointach (np. agregacje) potrafi „spłaszczyć” piki ruchu bez widocznej zmiany dla użytkownika. Kluczowe jest dobranie poprawnego klucza cache (proxy_cache_key) i rozważne obchodzenie się z nagłówkami Vary.

Przykładowy szkielet:

  • proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=app_cache:512m max_size=50g inactive=10m use_temp_path=off;
  • map $http_authorization $no_cache { default 1; „” 0; }
  • proxy_cache_bypass $no_cache; proxy_no_cache $no_cache; – wyłącz cache dla autoryzowanych żądań;
  • proxy_cache app_cache; proxy_cache_key „$scheme$request_method$host$request_uri”;
  • proxy_cache_valid 200 302 10s; proxy_cache_valid 404 1s; – krótkie okna dla dynamicznych zasobów;
  • proxy_cache_lock on; proxy_cache_lock_timeout 5s; – zapobiega stampede przy wygrzewaniu cache;
  • proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504; – lepiej dać lekko przeterminowaną odpowiedź niż błąd (w tle odświeżanie).

Dbaj o spójność: jeśli aplikacja zwraca Cache-Control: no-store/no-cache albo prywatne odpowiedzi, honoruj to przez proxy_ignore_headers z wyczuciem, ewentualnie w konkretnej location. Sygnały walidacji (ETag/Last-Modified) pomagają w conditional GET – Nginx prześle If-None-Match/If-Modified-Since do zaplecza i przyspieszy odpowiedzi 304.

Obrazy i duże pliki warto łączyć z mechanizmem slice (ngx_http_slice_module), by równomiernie obciążać upstream przy zimnym cache: slicing dzieli pobieranie na segmenty i buforuje je niezależnie. W dynamicznych mikrocache’ach pilnuj, by unikać wrażliwych na to endpointów (np. koszyk użytkownika), a jeśli musisz – rozszerz klucz cache o identyfikatory sesji.

Nadmiar logiki w warstwie Nginx (re-writing kluczy, warunki, mapy) utrudnia rozumienie i diagnostykę. Zaczynaj prosto, najpierw zabezpiecz stampede, egzekwuj limit rozmiaru cache i okresowe „czyszczenie” najstarszych wpisów, dopiero potem dodawaj wyjątki. W dużej skali rozważ segmentację cache per usługę (oddzielne keys_zone, katalogi i limity), by uniknąć wzajemnego wypierania danych.

TLS i HTTP/2/3 bez zadyszki

Szyfrowanie jest domyślnym standardem. Dobrze skonfigurowane TLS pozwala zredukować narzut CPU i opóźnienia, a także poprawić kompatybilność ze starszymi klientami, jeśli to konieczne. Podstawy:

  • ssl_protocols TLSv1.2 TLSv1.3; – wyłącz starsze protokoły;
  • ssl_prefer_server_ciphers on; – przy TLS 1.2;
  • ssl_ciphers zgodne z rekomendacjami (np. z mozilla SSL config), bez archaicznych szyfrów;
  • ssl_session_cache shared:SSL:100m; ssl_session_timeout 1h; – reuse sesji ogranicza pełne handshaki;
  • ssl_session_tickets on/off – włącz, jeśli poprawnie rotujesz klucze; w przeciwnym razie wyłącz;
  • ssl_stapling on; ssl_stapling_verify on; – OCSP stapling skraca czas weryfikacji certyfikatu;
  • resolver 1.1.1.1 1.0.0.1 valid=300s; resolver_timeout 5s; – niezbędny dla stapling.

HTTP/2 przyspiesza wiele równoległych zasobów przez jedną sesję, ale wymaga świadomej konfiguracji i testów. Dla Nginx:

  • listen 443 ssl http2; – włącz HTTP/2;
  • http2_max_concurrent_streams 128–256; – komfortowa wartość, zależna od klientów;
  • http2_recv_buffer_size i http2_chunk_size – zwykle domyślne wystarczą, ale przy masie małych plików możesz skorzystać z mniejszych kawałków.

W HTTP/3 z QUIC (gdy Twoja dystrybucja i build Nginx to wspierają) uruchomienie bywa proste (listen 443 quic reuseport;), ale stabilność i diagnostyka są trudniejsze. Zacznij od wybranej części ruchu (canary), monitoruj retry i fallback do H2/H1. Pamiętaj, że load balancer/edge może terminować H3 – Nginx wtedy widzi H2/H1.

Nie ignoruj konsekwencji HSTS i preloading – świetne dla bezpieczeństwa, ale wymuszają TLS dla całej domeny i subdomen. Starannie dobierz session reuse, bo zbyt agresywne resetowanie biletów sesji może zjadać więcej CPU niż oszczędza. W API czułych na opóźnienia monitoruj 95./99. percentyl TTFB po włączeniu H2/H3 – potencjalne regresje zwykle wynikają z niedopasowanych buforów lub przeciążonych workerów kryptografią.

Warto rozumieć, że korzyści z HTTP/2 rosną przy wielu małych zasobach (multiplexing), natomiast w monolitycznych payloadach zysk bywa marginalny. Wówczas lepsze jest ograniczenie liczby plików (bundling), rozsądne cache’owanie i kompresja niż gonitwa za nowym protokołem.

Obserwowalność, testy i bezpieczne wdrożenia

Bez rzetelnych danych łatwo „optymalizować” w ciemno. Włącz rozbudowany format logów i zbieraj metryki. Elementy, które pomogą ocenić skutki zmian:

  • log_format main '$remote_addr – $remote_user [$time_local] „$request” $status $body_bytes_sent $request_time $upstream_response_time $pipe „$http_referer” „$http_user_agent”’;
  • access_log /var/log/nginx/access.log main;
  • error_log /var/log/nginx/error.log warn; – nie ustawiaj poziomu na info w produkcji, chyba że tymczasowo.

Wbudowany status:

  • location /nginx_status { stub_status; allow 127.0.0.1; deny all; }

Da Ci to liczby aktywnych/oczekujących połączeń i szybkość przetwarzania. W większych instalacjach korzystaj z eksportera Prometheus (oficjalny nginx-prometheus-exporter czy VTS), a wizualizację z Grafany. Parametry, które powinny znaleźć się na pulpicie: liczba 4xx/5xx, saturacja workerów (active connections), odsetek 499 (zrywanie przez klienta), czasy odpowiedzi (p50/p95/p99) po endpointach, a także wykorzystanie dysku, jeśli intensywnie używasz cache na pliki.

Testy syntetyczne i profilowanie:

  • wrk, hey – szybkie testy H1; h2load – testy HTTP/2; k6 – scenariusze z JS i rozkładami ruchu;
  • tc/netem – wstrzykiwanie opóźnień/strat pakietów, by zobaczyć zachowanie przy mobilnych klientach;
  • perf/ebpf – gdy chcesz zajrzeć w kernel i zobaczyć, gdzie „pali się” CPU.

Zmiany wdrażaj bez przestojów: nginx -t do walidacji, a następnie nginx -s reload. Pamiętaj, że reload jest „graceful”: stare workery kończą obsługę aktywnych połączeń. Jeśli używasz systemd, ustaw KillMode=process i TimeoutStopSec rozsądnie. Dla dużych środowisk stosuj canary: najpierw część instancji lub ruchu, potem całość, a rollback trzymaj pod ręką. W kontenerach zadbaj o poprawne sygnały (TERM do gracefully stop), mechanizmy readiness/liveness i wspólne katalogi na certyfikaty oraz cache, jeśli potrzebujesz spójności.

W zakresie bezpieczeństwa twarde limity bywają równie ważne jak szybkość:

  • limit_conn_zone $binary_remote_addr zone=addr:10m; limit_conn addr 50; – ogranicz liczbę równoległych połączeń na IP;
  • limit_req_zone $binary_remote_addr zone=burst:10m rate=10r/s; limit_req zone=burst burst=20 nodelay; – podstawowa ochrona przed zalewem;
  • client_max_body_size 10m; – jasny limit uploadów;
  • server_tokens off; – nie ujawniaj wersji Nginx;
  • real_ip_header X-Forwarded-For; set_real_ip_from … – jeśli stoisz za load balancerem/CDN, by prawidłowo identyfikować klienta.

Nie ignoruj logiki biznesowej w aplikacji. Nawet najlepsza konfiguracja Nginx nie uratuje API, które zwraca niecache’owalne 2 MB JSON na każde żądanie. Tam, gdzie to możliwe, wprowadź paginację, ETag/Last-Modified, a w JS na froncie – conditional requests i caching.

Na koniec warto mieć checklistę utrzymaniową: okresowe odświeżanie certyfikatów, rotacja kluczy ticketów TLS, rotacja logów i kontrola zajętości dyskowej, sanity-check reloadu (czy nowe procesy weszły?), audyt wyjątków w konfiguracji (if w location, przepisywanie nagłówków). Pozwoli to uniknąć regresji, które często pojawiają się nie od razu, lecz po tygodniach.

Praktyczne receptury, pułapki i priorytety

Jeżeli zaczynasz, trzymaj się prostego planu wdrożeniowego. Każdy krok to mała zmiana i pomiar:

  • Włącz sendfile, tcp_nopush, tcp_nodelay; ustaw worker_processes auto i worker_connections 8192–16384;
  • Ustaw sensowne timeouts: client_body_timeout 10–30s, client_header_timeout 10s, keepalive_timeout 30–75s, send_timeout 30–60s;
  • Włącz gzip na typach tekstowych i rozważ umiarkowane brotli; obserwuj CPU;
  • Jeśli Nginx to reverse proxy – włącz proxy_buffering, dopasuj proxy_buffers, w upstream keepalive 64–256 i sensowne timeouts;
  • Dodaj mikrocache na bezstanowych endpointach (2–10s), z cache_lock i use_stale updating;
  • W TLS włącz session cache i OCSP stapling; wybierz nowoczesne szyfry; testuj HTTP/2;
  • Rozszerz log_format o czasy; włącz stub_status; zacznij zbierać metryki w Prometheus/Grafana;
  • Przetestuj canary i bezpieczny reload; utrzymuj checklistę.

Pułapki, o które łatwo zahaczyć:

  • Zbyt małe bufory nagłówków powodują trudne do odtworzenia 400; zbyt duże marnują pamięć;
  • Wyłączenie proxy_buffering przy wolnych klientach blokuje workerów – ustaw je selektywnie;
  • „Agresywna” kompresja na poziomie 7–9 zjada CPU z minimalnym zyskiem w rozmiarze;
  • Ustawienia HTTP/2 bez pomiarów – w niektórych profilach ruchu p95 może wzrosnąć;
  • Przekombinowany cache_key i ignorowanie nagłówków walidacji – trudne błędy koherencji;
  • Brak OCSP stapling z wydawcami o wolnych responderach – niepotrzebne opóźnienia w handshake;
  • Brak limitów (limit_req/limit_conn) – pojedynczy złośliwy klient może przeciążyć warstwę sieciową;
  • Nieprzemyślana rotacja logów – blokowanie dysku i poszatkowane analizy.

Jeśli zasoby sprzętowe są napięte, wybieraj zmiany o najlepszym stosunku „zysk/koszt”. W API krytyczna jest niska p95; w serwowaniu assetów – throughput i CPU/GB przesłany. Mierz i redukuj kolejne źródła opóźnień w kolejności: DNS/certyfikaty (stapling), handshake i session reuse, kompresja, buforowanie, cache treści, optymalizacja upstream. W każdej iteracji sprawdzaj, czy profil awarii (co się stanie, gdy upstream padnie?) jest akceptowalny – use_stale pomaga, ale warto też mieć mechanizmy circuit-breaker po stronie aplikacji.

Dla ruchu o mieszanych charakterystykach (API + assety + strumienie) rozważ separację konfiguracji: oddzielne serwery w Nginx (server {}), a nawet oddzielne instancje. Pozwoli to izolować limity, bufory i cache. W skali horyzontalnej utrzymuj spójne wersje Nginx i modułów, automatyzuj rollouty i weryfikację (nginx -t w CI), a pliki konfiguracyjne dziel na małe, logiczne include’y.

Patrząc szerzej, optymalizacja Nginx to część większego układu. Często największy skok daje poprawa aplikacji albo przesunięcie logiki na brzeg – np. pre-rendering treści, strategia cache na CDN, mądre versionowanie assetów. Rolą Nginx jest wtedy sprawne i przewidywalne „krojenie” ruchu, kompresja, terminacja szyfrowania i solidne proxy z mechanizmami ochronnymi.

Na koniec warto zebrać najważniejsze hasła: utrzymuj proste, mierzalne cele; dodawaj zmiany iteracyjnie; ufaj metrykom bardziej niż intuicji; testuj pod obciążeniem, które przypomina produkcję; automatyzuj weryfikację i rollback; zapisuj wiedzę i powtarzalne wzorce w repozytorium konfiguracji. Dzięki temu Twoje wdrożenia będą przewidywalne, a Nginx pozostanie szybką i stabilną warstwą frontową.

Gdy zrobisz pierwszy krok i zobaczysz wymierny spadek p95/p99, dodaj do pulpitu metryki, które informują o zmianach w czasie – to Twoje monitorowanie. Tylko ono da Ci pewność, że kolejne optymalizacje działają, a przypadkowa zmiana nie zniweczyła wcześniejszego wysiłku. Z tą praktyką każda następna iteracja będzie łatwiejsza, a Nginx odwdzięczy się przewidywalną pracą nawet pod znacznymi pikami ruchu.

Chcesz mieć dobrą stronę internetową?

Zadzwoń do nas. Porozmawiamy o stronie dopasowanej
do Twoich potrzeb.

601 162 666

Poprzedni wpis
Tworzenie stron www Zakopane
Następny wpis
Strona internetowa na WordPress dla kolonii letnich
Zadzwoń Konsultacja