<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Андрей Викулов (VProger)</title>
    <description>The latest articles on Forem by Андрей Викулов (VProger) (@_vproger_).</description>
    <link>https://forem.com/_vproger_</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3736842%2Fa729d334-348d-49b8-90df-eeca7e767b29.png</url>
      <title>Forem: Андрей Викулов (VProger)</title>
      <link>https://forem.com/_vproger_</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/_vproger_"/>
    <language>en</language>
    <item>
      <title>Как создать блог на Astro: установка, MDX, Content Collections</title>
      <dc:creator>Андрей Викулов (VProger)</dc:creator>
      <pubDate>Tue, 21 Apr 2026 18:21:53 +0000</pubDate>
      <link>https://forem.com/_vproger_/kak-sozdat-blogh-na-astro-ustanovka-mdx-content-collections-3000</link>
      <guid>https://forem.com/_vproger_/kak-sozdat-blogh-na-astro-ustanovka-mdx-content-collections-3000</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fymgh3nec0ehg518w1np8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fymgh3nec0ehg518w1np8.png" alt="Как создать блог на Astro: установка, MDX, Content Collections" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Как создать блог на Astro: полная инструкция&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Проблема:&lt;/strong&gt; нужно быстро поднять быстрый блог или контентный сайт с хорошим SEO, но без сложностей Next.js и без перегруженного JavaScript.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Решение:&lt;/strong&gt; Astro — статический генератор (SSG), который рендерит HTML по умолчанию и добавляет интерактивность точечно (islands architecture). В итоге получается лёгкая страница, понятная поисковикам и пользователям.&lt;/p&gt;

&lt;p&gt;В этой статье — пошаговая настройка блога на Astro: установка, структура проекта, маршруты, &lt;strong&gt;MDX&lt;/strong&gt; (Markdown с компонентами) и &lt;strong&gt;Content Collections&lt;/strong&gt; со схемой на Zod для валидации контента.&lt;/p&gt;




&lt;p&gt;В чём проблема: выбор фреймворка для контентного сайта&lt;/p&gt;

&lt;p&gt;Если нужен блог, документация или маркетинговый сайт, есть три основных варианта:&lt;/p&gt;

&lt;p&gt;| Критерий | Astro | Next.js | Gatsby |&lt;/p&gt;

&lt;p&gt;|----------|-------|---------|--------|&lt;/p&gt;

&lt;p&gt;| &lt;strong&gt;Модель по умолчанию&lt;/strong&gt; | SSG, HTML-first | SSR/SSG, JS-heavy | SSG (React) |&lt;/p&gt;

&lt;p&gt;| &lt;strong&gt;JS в браузере&lt;/strong&gt; | Минимум (islands) | Много (гидрация всего) | Много (React-дерево) |&lt;/p&gt;

&lt;p&gt;| &lt;strong&gt;Контент из коробки&lt;/strong&gt; | MDX, Markdown, Collections | Нужны решения | GraphQL + плагины |&lt;/p&gt;

&lt;p&gt;| &lt;strong&gt;Скорость контент-сайта&lt;/strong&gt; | Очень высокая | Зависит от настроек | Хорошая после билда |&lt;/p&gt;

&lt;p&gt;| &lt;strong&gt;Кривая обучения&lt;/strong&gt; | Низкая (HTML + островки) | Выше (React, App Router) | Выше (GraphQL, плагины) |&lt;/p&gt;

&lt;p&gt;| &lt;strong&gt;Лучше всего под&lt;/strong&gt; | Блог, доки, лендинг, портфолио | SPA, приложения, сложный роутинг | Контент на React + CMS |&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Вывод:&lt;/strong&gt; для блога, документации или маркетингового сайта Astro часто даёт самый лёгкий результат при минимальном JavaScript.&lt;/p&gt;




&lt;p&gt;Рабочее решение: пошаговая настройка&lt;/p&gt;

&lt;p&gt;Шаг 1: Создание проекта Astro&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
Официальный способ: интерактивный мастер

npm create astro@latest

или

pnpm create astro@latest

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Мастер спросит:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Шаблон (выбери blog для блога или minimal для чистого старта)&lt;/li&gt;
&lt;li&gt;TypeScript (рекомендуется включить — пригодится для Content Collections)&lt;/li&gt;
&lt;li&gt;ESLint и форматирование (по желанию)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Шаг 2: Проверка системных требований&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
Проверить Node.js &lt;span class="o"&gt;(&lt;/span&gt;должно быть &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; 18&lt;span class="o"&gt;)&lt;/span&gt;

node &lt;span class="nt"&gt;-v&lt;/span&gt;

Проверить менеджер пакетов

npm &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="c"&gt;# или pnpm -v / yarn -v&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Требования:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node.js 18+ (рекомендуется LTS: 20 или 22)&lt;/li&gt;
&lt;li&gt;npm, pnpm или yarn&lt;/li&gt;
&lt;li&gt;Редактор кода (VS Code + плагин Astro)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Шаг 3: Запуск dev-сервера&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
Перейти в папку проекта

&lt;span class="nb"&gt;cd&lt;/span&gt; &amp;lt;имя-проекта&amp;gt;

Запустить dev-сервер

npm run dev

или

pnpm dev

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Открой &lt;a href="http://localhost:4321" rel="noopener noreferrer"&gt;http://localhost:4321&lt;/a&gt; — должен открыться стартовый сайт.&lt;/p&gt;

&lt;p&gt;Шаг 4: Проверка сборки перед деплоем&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
Собрать проект в папку dist/

npm run build

или

pnpm build

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Если сборка прошла без ошибок — проект готов к деплою.&lt;/p&gt;




&lt;p&gt;Структура проекта Astro&lt;/p&gt;

&lt;p&gt;Типичная структура блога на Astro:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
/

├─ astro.config.mjs # Конфигурация Astro

├─ package.json # Зависимости и скрипты

├─ src/

│ ├─ pages/ # Маршруты (file-based routing)

│ │ ├─ index.astro # Главная страница

│ │ ├─ about.astro # /about

│ │ └─ blog/

│ │ ├─ index.astro # /blog (список статей)

│ │ └─ [slug].astro # /blog/:slug (страница статьи)

│ ├─ layouts/ # Layouts (общая обвязка)

│ │ └─ BaseLayout.astro

│ ├─ components/ # UI компоненты

│ │ └─ Callout.astro

│ ├─ content/ # Контент (Content Collections)

│ │ └─ blog/ # Статьи блога

│ └─ styles/ # Глобальные стили

└─ public/ # Статика (копируется как есть)

└─ favicon.svg

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Правило маршрутизации:&lt;/strong&gt; файл в src/pages/ = маршрут.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;src/pages/index.astro → /&lt;/li&gt;
&lt;li&gt;src/pages/about.astro → /about&lt;/li&gt;
&lt;li&gt;src/pages/blog/[slug].astro → /blog/:slug&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Первый layout: BaseLayout.astro&lt;/p&gt;

&lt;p&gt;Создай файл src/layouts/BaseLayout.astro:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
---

// Пропсы приходят со страницы; задаём значения по умолчанию

const {

title = "Site",

description = "Astro site",

} = Astro.props;

---

&amp;lt;!doctype html&amp;gt;

&amp;lt;html lang="ru"&amp;gt;

&amp;lt;head&amp;gt;

&amp;lt;meta charset="utf-8" /&amp;gt;

&amp;lt;meta name="viewport" content="width=device-width, initial-scale=1" /&amp;gt;

&amp;lt;title&amp;gt;{title}&amp;lt;/title&amp;gt;

&amp;lt;meta name="description" content={description} /&amp;gt;

&amp;lt;/head&amp;gt;

&amp;lt;body&amp;gt;

&amp;lt;header style="padding:16px; border-bottom:1px solid rgba(255,255,255,.1)"&amp;gt;

[Главная](/)

&amp;lt;span style="opacity:.6; margin-left:8px"&amp;gt;|&amp;lt;/span&amp;gt;

[Блог](/blog)

&amp;lt;/header&amp;gt;

&amp;lt;main style="max-width: 860px; margin: 0 auto; padding: 24px;"&amp;gt;

&amp;lt;slot /&amp;gt;

&amp;lt;/main&amp;gt;

&amp;lt;footer style="padding:16px; border-top:1px solid rgba(255,255,255,.1); opacity:.7"&amp;gt;

© {new Date().getFullYear()}

&amp;lt;/footer&amp;gt;

&amp;lt;/body&amp;gt;

&amp;lt;/html&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt; — это "сюда вставится содержимое страницы".&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Первая страница: index.astro&lt;/p&gt;

&lt;p&gt;Создай файл src/pages/index.astro:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
---

import BaseLayout from "../layouts/BaseLayout.astro";

---

&amp;lt;BaseLayout title="Главная" description="Стартовый проект на Astro"&amp;gt;

# Привет, Astro

Если страница открылась — ты уже в деле.

&amp;lt;/BaseLayout&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Открой &lt;a href="http://localhost:4321" rel="noopener noreferrer"&gt;http://localhost:4321&lt;/a&gt; — должна отобразиться главная страница.&lt;/p&gt;




&lt;p&gt;Подключаем MDX: Markdown с компонентами&lt;/p&gt;

&lt;p&gt;Установка MDX&lt;/p&gt;

&lt;p&gt;MDX — это Markdown, который умеет &lt;strong&gt;встраивать компоненты&lt;/strong&gt; и JS-выражения.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
npx astro add mdx

или

pnpm astro add mdx

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;После установки в astro.config.mjs появится интеграция @astrojs/mdx.&lt;/p&gt;

&lt;p&gt;Первая MDX-страница&lt;/p&gt;

&lt;p&gt;Создай файл src/pages/guide.mdx:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
---

title: "Гайд по Astro"

description: "Пробная MDX-страница"

---

MDX в Astro работает ✅

Это обычный Markdown… но с бонусами.

- списки
- код
- и компоненты ниже

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Открой /guide — должна отрендериться страница.&lt;/p&gt;




&lt;p&gt;Встраиваем компоненты в MDX&lt;/p&gt;

&lt;p&gt;Создаём компонент Callout.astro&lt;/p&gt;

&lt;p&gt;Создай файл src/components/Callout.astro:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
---

const { type = "info", title = "Важно" } = Astro.props;

const styles = {

info: "background: rgba(0, 180, 255, .08); border: 1px solid rgba(0, 180, 255, .35);",

warn: "background: rgba(255, 180, 0, .08); border: 1px solid rgba(255, 180, 0, .35);",

danger: "background: rgba(255, 80, 80, .08); border: 1px solid rgba(255, 80, 80, .35);",

};

---

&amp;lt;section style={padding: 14px 16px; border-radius: 12px; ${styles[type] ?? styles.info}}&amp;gt;

&amp;lt;strong style="display:block; margin-bottom: 6px"&amp;gt;{title}&amp;lt;/strong&amp;gt;

&amp;lt;div&amp;gt;&amp;lt;slot /&amp;gt;&amp;lt;/div&amp;gt;

&amp;lt;/section&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Используем компонент в MDX&lt;/p&gt;

&lt;p&gt;Обнови src/pages/guide.mdx:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
---

title: "Гайд по Astro"

description: "Пробная MDX-страница"

---

import Callout from "../components/Callout.astro";

MDX + компоненты

&amp;lt;Callout type="info" title="Смысл MDX"&amp;gt;

Ты пишешь статью как Markdown, но можешь вставлять "живые" компоненты.

&amp;lt;/Callout&amp;gt;

&amp;lt;Callout type="warn" title="Пара слов о порядке"&amp;gt;

Не превращай статью в React-приложение. Это всё ещё контент.

&amp;lt;/Callout&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Content Collections: контент с валидацией&lt;/p&gt;

&lt;p&gt;Зачем нужны collections&lt;/p&gt;

&lt;p&gt;Чтобы:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Описать &lt;strong&gt;схему фронтматтера&lt;/strong&gt; (какие поля обязательны)&lt;/li&gt;
&lt;li&gt;Ловить ошибки &lt;strong&gt;на этапе билда&lt;/strong&gt; (а не в проде)&lt;/li&gt;
&lt;li&gt;Получать &lt;strong&gt;типизацию&lt;/strong&gt; (особенно приятно в TS)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Настраиваем src/content.config.ts&lt;/p&gt;

&lt;p&gt;Создай файл src/content.config.ts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;
&lt;span class="c1"&gt;// src/content.config.ts&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineCollection&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;astro:content&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;astro/zod&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defineCollection&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;

&lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;

&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

&lt;span class="na"&gt;pubDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;

&lt;span class="na"&gt;updatedDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;

&lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;([]),&lt;/span&gt;

&lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Frontend&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

&lt;span class="na"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

&lt;span class="na"&gt;cover&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;

&lt;span class="na"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

&lt;span class="p"&gt;}),&lt;/span&gt;

&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;collections&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;blog&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;astro/zod — это реэкспорт Zod для схем коллекций.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Первая статья в коллекции&lt;/p&gt;

&lt;p&gt;Создай файл src/content/blog/astro-part-1.mdx:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
---

title: "Astro: старт (часть 1)"

description: "Статья в content collection, чтобы потом красиво собрать список и страницы."

pubDate: 2026-01-05T16:00:00.000Z

tags:

- astro
- mdx
- frontend

category: Frontend

draft: false

slug: "astro-part-1"

---

import Callout from "../../components/Callout.astro";

Astro: старт (часть 1)

&amp;lt;Callout type="info" title="Это пост из коллекции"&amp;gt;

Он лежит в src/content/blog и валидируется схемой.

&amp;lt;/Callout&amp;gt;

Здесь начинается содержание статьи…

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Выводим список статей: /blog&lt;/p&gt;

&lt;p&gt;Создай файл src/pages/blog/index.astro:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
---

import BaseLayout from "../../layouts/BaseLayout.astro";

import { getCollection } from "astro:content";

// Загружаем все посты, скрываем черновики, сортируем по дате

const posts = (await getCollection("blog"))

.filter(p =&amp;gt; !p.data.draft)

.sort((a, b) =&amp;gt; b.data.pubDate.valueOf() - a.data.pubDate.valueOf());

---

&amp;lt;BaseLayout title="Блог" description="Список статей на Astro"&amp;gt;

# Блог

{posts.map((post) =&amp;gt; (

- [{post.data.title}]({/blog/${post.data.slug}/})

))}

&amp;lt;/BaseLayout&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Страница статьи: /blog/:slug&lt;/p&gt;

&lt;p&gt;Создай файл src/pages/blog/[slug].astro:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
---

import BaseLayout from "../../layouts/BaseLayout.astro";

import { getCollection } from "astro:content";

// Для SSG Astro нужен список всех путей

export async function getStaticPaths() {

const posts = await getCollection("blog");

return posts

.filter(p =&amp;gt; !p.data.draft)

.map((post) =&amp;gt; ({

params: { slug: post.data.slug },

props: { post },

}));

}

const { post } = Astro.props;

// У коллекционных MDX/MD есть render() — получаем компонент контента

const { Content } = await post.render();

---

&amp;lt;BaseLayout title={post.data.title} description={post.data.description}&amp;gt;

&amp;lt;article&amp;gt;

# {post.data.title}

{post.data.pubDate.toLocaleDateString("ru-RU")}

&amp;lt;Content /&amp;gt;

&amp;lt;/article&amp;gt;

&amp;lt;/BaseLayout&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Проверка результата&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Проверка списка статей
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
Запустить dev-сервер

npm run dev

Открыть http://localhost:4321/blog

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Должен отобразиться список статей из src/content/blog/.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Проверка страницы статьи
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
Открыть http://localhost:4321/blog/astro-part-1/

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Должна отобразиться страница статьи с содержимым MDX.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Проверка сборки
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
Собрать проект

npm run build

Проверить dist/

&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; dist/

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Если сборка прошла без ошибок — все маршруты и коллекции настроены верно.&lt;/p&gt;




&lt;p&gt;Типичные ошибки&lt;/p&gt;

&lt;p&gt;❌ Ошибка: getCollection("blog") — коллекция не найдена&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Причина:&lt;/strong&gt; Нет src/content.config.ts или папки src/content/blog/.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Решение:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
Создать конфиг

&lt;span class="nb"&gt;touch &lt;/span&gt;src/content.config.ts

Создать папку для статей

&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; src/content/blog

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;❌ Ошибка валидации frontmatter при билде&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Причина:&lt;/strong&gt; Поля не совпадают со схемой (дата, обязательные поля).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Решение:&lt;/strong&gt; Проверить title, description, pubDate, slug в .mdx. Даты в формате ISO: 2026-01-05T16:00:00.000Z.&lt;/p&gt;

&lt;p&gt;❌ MDX-страница не открывается / 404&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Причина:&lt;/strong&gt; Файл не в src/pages/ или опечатка в имени.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Решение:&lt;/strong&gt; Роут = путь от pages: guide.mdx → /guide.&lt;/p&gt;

&lt;p&gt;❌ Компонент в MDX не найден&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Причина:&lt;/strong&gt; Неверный путь импорта (относительно файла MDX).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Решение:&lt;/strong&gt; Импорт от корня файла: ../../components/Callout.astro.&lt;/p&gt;

&lt;p&gt;❌ post.render is not a function&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Причина:&lt;/strong&gt; Используется сырой объект поста вместо результата getCollection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Решение:&lt;/strong&gt; В [slug].astro брать post из getStaticPaths → props и вызывать post.render().&lt;/p&gt;




&lt;p&gt;Где применять&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Блоги:&lt;/strong&gt; контентные сайты с MDX и Collections&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Документация:&lt;/strong&gt; техническая документация с компонентами&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Лендинги:&lt;/strong&gt; маркетинговые страницы с быстрым SEO&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Портфолио:&lt;/strong&gt; сайты-визитки с минимальным JS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Связанные статьи:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SEO, sitemap и RSS в Astro&lt;/li&gt;
&lt;li&gt;Actions, API Routes, SSR и деплой&lt;/li&gt;
&lt;li&gt;TypeScript: основы и философия&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Документация:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.astro.build/" rel="noopener noreferrer"&gt;Astro Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.astro.build/en/guides/markdown-content/" rel="noopener noreferrer"&gt;MDX Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.astro.build/en/guides/content-collections/" rel="noopener noreferrer"&gt;Content Collections&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;a href="https://viku-lov.ru/blog/astro-blog-setup-mdx-content-collections" rel="noopener noreferrer"&gt;Read more on viku-lov.ru&lt;/a&gt;&lt;/p&gt;

</description>
      <category>astro</category>
      <category>frontend</category>
      <category>blog</category>
      <category>mdx</category>
    </item>
    <item>
      <title>Как конвертировать CHM в один PDF на Linux: без мусора и битых ссылок</title>
      <dc:creator>Андрей Викулов (VProger)</dc:creator>
      <pubDate>Sat, 28 Feb 2026 08:04:06 +0000</pubDate>
      <link>https://forem.com/_vproger_/kak-konviertirovat-chm-v-odin-pdf-na-linux-biez-musora-i-bitykh-ssylok-3edd</link>
      <guid>https://forem.com/_vproger_/kak-konviertirovat-chm-v-odin-pdf-na-linux-biez-musora-i-bitykh-ssylok-3edd</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwl7mqlbqlo3b8s0gxi78.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwl7mqlbqlo3b8s0gxi78.png" alt="Как конвертировать CHM в один PDF на Linux: без мусора и битых ссылок" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Как конвертировать CHM в один PDF на Linux: без мусора и битых ссылок&lt;/p&gt;

&lt;p&gt;Если у тебя есть bsm_api.chm и нужно получить &lt;strong&gt;один нормальный PDF&lt;/strong&gt; , а не «папку из 900 HTML + слёзы», вот рабочий пайплайн: &lt;strong&gt;CHM → HTML → (сборка) → PDF&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;В конце будет:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;один manual.pdf&lt;/li&gt;
&lt;li&gt;без битых внутренних ссылок (насколько позволяет исходник)&lt;/li&gt;
&lt;li&gt;с нормальной навигацией/оглавлением (если CHM адекватный)&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;В чём проблема&lt;/p&gt;

&lt;p&gt;CHM — это не “один файл с текстом”, а контейнер: HTML + картинки + оглавление + внутренние ссылки.&lt;/p&gt;

&lt;p&gt;Типовые боли:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;конвертеры делают &lt;strong&gt;1000 страниц&lt;/strong&gt; вместо одного PDF&lt;/li&gt;
&lt;li&gt;ломают ссылки и кодировки&lt;/li&gt;
&lt;li&gt;получаешь PDF, который выглядит как скриншоты из 2007&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Рабочее решение&lt;/p&gt;

&lt;p&gt;1) Установка инструментов&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ubuntu/Debian:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update

&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;

extractchm &lt;span class="se"&gt;\&lt;/span&gt;

wkhtmltopdf &lt;span class="se"&gt;\&lt;/span&gt;

python3 &lt;span class="se"&gt;\&lt;/span&gt;

python3-bs4 &lt;span class="se"&gt;\&lt;/span&gt;

python3-lxml

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Если extractchm нет (редко, но бывает), ставим из chmlib:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; chmlib-tools

потом будет утилита extract&lt;span class="se"&gt;\_&lt;/span&gt;chmLib

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Проверка:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
extractchm &lt;span class="nt"&gt;-h&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true

&lt;/span&gt;wkhtmltopdf &lt;span class="nt"&gt;--version&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;2) Распаковать CHM в HTML&lt;/p&gt;

&lt;p&gt;Создай рабочую папку:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/work/chm2pdf/&lt;span class="o"&gt;{&lt;/span&gt;src,html,out&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;cp &lt;/span&gt;bsm&lt;span class="se"&gt;\_&lt;/span&gt;api.chm ~/work/chm2pdf/src/

&lt;span class="nb"&gt;cd&lt;/span&gt; ~/work/chm2pdf

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Распаковка:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Вариант A (extractchm):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
extractchm &lt;span class="nt"&gt;-o&lt;/span&gt; html src/bsm&lt;span class="se"&gt;\_&lt;/span&gt;api.chm

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Вариант B (extract_chmLib):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
extract&lt;span class="se"&gt;\_&lt;/span&gt;chmLib src/bsm&lt;span class="se"&gt;\_&lt;/span&gt;api.chm html

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Проверка что распаковалось:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; html | &lt;span class="nb"&gt;head

&lt;/span&gt;find html &lt;span class="nt"&gt;-maxdepth&lt;/span&gt; 2 &lt;span class="nt"&gt;-type&lt;/span&gt; f | &lt;span class="nb"&gt;head&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;3) Найти “точку входа” (главную страницу)&lt;/p&gt;

&lt;p&gt;Обычно это index.html, default.html, start.html или что-то из оглавления.&lt;/p&gt;

&lt;p&gt;Быстрый поиск кандидатов:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
&lt;span class="nb"&gt;ls &lt;/span&gt;html | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-iE&lt;/span&gt; &lt;span class="s1"&gt;'index|default|start|main'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Если не очевидно — ищем самый “толстый” HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
find html &lt;span class="nt"&gt;-type&lt;/span&gt; f &lt;span class="nt"&gt;-iname&lt;/span&gt; &lt;span class="s1"&gt;'\*.html'&lt;/span&gt; &lt;span class="nt"&gt;-printf&lt;/span&gt; &lt;span class="s1"&gt;'%s\t%p\n'&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-20&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;4) Собрать единый HTML (важный шаг)&lt;/p&gt;

&lt;p&gt;wkhtmltopdf лучше работает, когда ты отдаёшь ему &lt;strong&gt;одну HTML-страницу&lt;/strong&gt; , а не тысячу.&lt;/p&gt;

&lt;p&gt;Сделаем “склейку”: берём список страниц и собираем в out/merged.html.&lt;/p&gt;

&lt;p&gt;Вот минимальный Python-скрипт, который:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;читает оглавление (если найдём файл типа .hhc/toc)&lt;/li&gt;
&lt;li&gt;если оглавления нет — склеивает HTML по алфавиту (хуже, но работает)&lt;/li&gt;
&lt;li&gt;вырезает мусорные script/iframe&lt;/li&gt;
&lt;li&gt;приводит относительные ссылки к локальным путям&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Создай файл tools/merge.py:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;
&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;bs4&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BeautifulSoup&lt;/span&gt;

&lt;span class="n"&gt;ROOT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;OUT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;out&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;OUT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exist&lt;/span&gt;\&lt;span class="n"&gt;_ok&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;\&lt;span class="n"&gt;_html&lt;/span&gt;\&lt;span class="nf"&gt;_files&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;

&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Если&lt;/span&gt; &lt;span class="n"&gt;есть&lt;/span&gt; &lt;span class="n"&gt;оглавление&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="n"&gt;можно&lt;/span&gt; &lt;span class="n"&gt;допилить&lt;/span&gt; &lt;span class="n"&gt;парсер&lt;/span&gt; &lt;span class="n"&gt;под&lt;/span&gt; &lt;span class="n"&gt;конкретный&lt;/span&gt; &lt;span class="n"&gt;CHM&lt;/span&gt;

&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Универсальный&lt;/span&gt; &lt;span class="n"&gt;fallback&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="n"&gt;склеим&lt;/span&gt; &lt;span class="n"&gt;HTML&lt;/span&gt; &lt;span class="n"&gt;по&lt;/span&gt; &lt;span class="n"&gt;имени&lt;/span&gt;

&lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ROOT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rglob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\*.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;выкинем&lt;/span&gt; &lt;span class="n"&gt;дубли&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;служебное&lt;/span&gt;

&lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ow"&gt;is&lt;/span&gt;\&lt;span class="nf"&gt;_file&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;st&lt;/span&gt;\&lt;span class="n"&gt;_size&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;clean&lt;/span&gt;\&lt;span class="nf"&gt;_body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;\&lt;span class="n"&gt;_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;\&lt;span class="n"&gt;_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;\&lt;span class="nf"&gt;_bytes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;грубая&lt;/span&gt; &lt;span class="n"&gt;попытка&lt;/span&gt; &lt;span class="n"&gt;с&lt;/span&gt; &lt;span class="n"&gt;кодировкой&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;enc&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cp1251&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;windows-1251&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;latin-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;break&lt;/span&gt;

&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;UnicodeDecodeError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="k"&gt;continue&lt;/span&gt;

&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ignore&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;soup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BeautifulSoup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lxml&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;выкидываем&lt;/span&gt; &lt;span class="n"&gt;потенциальный&lt;/span&gt; &lt;span class="n"&gt;мусор&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;soup&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;script&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;iframe&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;noscript&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;

&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decompose&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;soup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;soup&lt;/span&gt;

&lt;span class="n"&gt;нормализуем&lt;/span&gt; &lt;span class="n"&gt;якоря&lt;/span&gt; &lt;span class="n"&gt;и&lt;/span&gt; &lt;span class="n"&gt;локальные&lt;/span&gt; &lt;span class="n"&gt;ресурсы&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find&lt;/span&gt;\&lt;span class="nf"&gt;_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

&lt;span class="n"&gt;href&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;href&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;убираем&lt;/span&gt; &lt;span class="n"&gt;внешние&lt;/span&gt; &lt;span class="n"&gt;и&lt;/span&gt; &lt;span class="n"&gt;mailto&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mailto:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;

&lt;span class="k"&gt;continue&lt;/span&gt;

&lt;span class="n"&gt;normalize&lt;/span&gt; &lt;span class="n"&gt;windows&lt;/span&gt; &lt;span class="n"&gt;slashes&lt;/span&gt;

&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;href&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;добавим&lt;/span&gt; &lt;span class="n"&gt;заголовок&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;разделитель&lt;/span&gt;

&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;soup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;\&lt;span class="nf"&gt;_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;soup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;\&lt;span class="n"&gt;_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;

&lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;

# &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;

&lt;span class="n"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;\&lt;span class="n"&gt;_html&lt;/span&gt;\&lt;span class="nf"&gt;_files&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clean&lt;/span&gt;\&lt;span class="nf"&gt;_body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;skip &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;merged&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;!doctype html&amp;gt;

&amp;lt;html&amp;gt;

&amp;lt;head&amp;gt;

&amp;lt;meta charset=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; /&amp;gt;

&amp;lt;title&amp;gt;CHM merged&amp;lt;/title&amp;gt;

&amp;lt;style&amp;gt;

body {{ font-family: Arial, sans-serif; line-height: 1.45; }}

pre, code {{ white-space: pre-wrap; word-wrap: break-word; }}

h1 {{ page-break-before: always; }}

&amp;lt;/style&amp;gt;

&amp;lt;/head&amp;gt;

&amp;lt;body&amp;gt;

&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;

&amp;lt;/body&amp;gt;

&amp;lt;/html&amp;gt;

&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OUT&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;merged.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;\&lt;span class="nf"&gt;_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OK -&amp;gt; out/merged.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; \&lt;span class="n"&gt;_&lt;/span&gt;\&lt;span class="n"&gt;_name&lt;/span&gt;\&lt;span class="n"&gt;_&lt;/span&gt;\&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\_\_main\_\_&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Запуск:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; tools out

python3 tools/merge.py

&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; out/merged.html

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;5) Конвертировать merged.html → PDF&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
wkhtmltopdf &lt;span class="se"&gt;\&lt;/span&gt;

&lt;span class="nt"&gt;--enable-local-file-access&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;

&lt;span class="nt"&gt;--encoding&lt;/span&gt; utf-8 &lt;span class="se"&gt;\&lt;/span&gt;

&lt;span class="nt"&gt;--page-size&lt;/span&gt; A4 &lt;span class="se"&gt;\&lt;/span&gt;

&lt;span class="nt"&gt;--margin-top&lt;/span&gt; 12 &lt;span class="nt"&gt;--margin-bottom&lt;/span&gt; 12 &lt;span class="nt"&gt;--margin-left&lt;/span&gt; 10 &lt;span class="nt"&gt;--margin-right&lt;/span&gt; 10 &lt;span class="se"&gt;\&lt;/span&gt;

out/merged.html out/manual.pdf

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Проверка результата:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-lah&lt;/span&gt; out/manual.pdf

file out/manual.pdf

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Проверка результата&lt;/p&gt;

&lt;p&gt;Минимум:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;PDF открывается, вес адекватный (не 2 KB и не 2 GB)&lt;/li&gt;
&lt;li&gt;есть текст (не “картинки страниц”)&lt;/li&gt;
&lt;li&gt;код/таблицы читаются&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Быстрый sanity-check: извлечь пару строк текста:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
python3 - &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;PY&lt;/span&gt;&lt;span class="sh"&gt;'

import subprocess, sys

p = subprocess.run(["pdftotext", "out/manual.pdf", "-"], capture&lt;/span&gt;&lt;span class="se"&gt;\_&lt;/span&gt;&lt;span class="sh"&gt;output=True, text=True)

print(p.stdout[:800])
&lt;/span&gt;&lt;span class="no"&gt;
PY

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Если pdftotext нет:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; poppler-utils

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Типичные ошибки&lt;/p&gt;

&lt;p&gt;❌ 1) “Blocked access to file” / не видит картинки&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Причина:&lt;/strong&gt; wkhtmltopdf по умолчанию режет локальные ресурсы.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Решение:&lt;/strong&gt; добавь --enable-local-file-access (в примере уже есть).&lt;/p&gt;




&lt;p&gt;❌ 2) Кракозябры вместо русского&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Причина:&lt;/strong&gt; HTML в CP1251, а ты склеил как UTF-8.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Решение:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;в merge.py мы пробуем cp1251/windows-1251&lt;/li&gt;
&lt;li&gt;если всё равно плохо — найди реальную кодировку исходников:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
file &lt;span class="nt"&gt;-i&lt;/span&gt; html/&lt;span class="se"&gt;\*&lt;/span&gt;.html | &lt;span class="nb"&gt;head&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;❌ 3) “Оглавление” нет, порядок глав сломан&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Причина:&lt;/strong&gt; CHM хранит структуру в .hhc/.hhk, а мы склеили “по алфавиту”.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Решение:&lt;/strong&gt; дописать парсер оглавления под конкретный CHM (реально 20–40 строк, если структура нормальная).&lt;/p&gt;

&lt;p&gt;Если хочешь — кидай список файлов из html/ (без содержимого), я подскажу точечно.&lt;/p&gt;




&lt;p&gt;Где применять&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Локально:&lt;/strong&gt; быстро получить PDF, отдать в LLM/коллегам/заказчику.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI/CD:&lt;/strong&gt; автогенерация документации в PDF (если CHM как артефакт).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VPS:&lt;/strong&gt; можно крутить без GUI, чисто консолью.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Смотри также&lt;/p&gt;

&lt;p&gt;Если будешь ковыряться с файлами и поиском по дереву — пригодится:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Поиск файлов в Linux: команды для Ubuntu/CentOS — диагностика и find&lt;/li&gt;
&lt;li&gt;find: поиск больших файлов — когда PDF внезапно 900MB&lt;/li&gt;
&lt;li&gt;rsync: безопасное копирование с прогрессом — перенос распакованного CHM или готового PDF&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://viku-lov.ru/blog/kak-konvertirovat-chm-v-odin-pdf-linux" rel="noopener noreferrer"&gt;Read more on viku-lov.ru&lt;/a&gt;&lt;/p&gt;

</description>
      <category>linux</category>
      <category>documentation</category>
      <category>pdf</category>
      <category>chm</category>
    </item>
    <item>
      <title>Миграция с WordPress на Bitrix без потери SEO</title>
      <dc:creator>Андрей Викулов (VProger)</dc:creator>
      <pubDate>Mon, 23 Feb 2026 19:00:40 +0000</pubDate>
      <link>https://forem.com/_vproger_/mighratsiia-s-wordpress-na-bitrix-biez-potieri-seo-2l45</link>
      <guid>https://forem.com/_vproger_/mighratsiia-s-wordpress-na-bitrix-biez-potieri-seo-2l45</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fam3d01p5lcaterkncknu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fam3d01p5lcaterkncknu.png" alt="Миграция с WordPress на Bitrix без потери SEO" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Миграция с WordPress на Bitrix без потери SEO&lt;/p&gt;

&lt;p&gt;Поисковый запрос «миграция с WordPress на Bitrix» обычно означает одно: нужно перенести проект без падения трафика и без пересборки индекса с нуля. Статья для тех, кто уже решил перейти на Bitrix и кому важно не потерять позиции и переходы из поиска.&lt;/p&gt;

&lt;p&gt;Цель — сохранить структуру URL, метаданные (title, description, canonical), пользователей и поведенческие факторы. Ниже — конкретные шаги: экспорт из БД WordPress, импорт в Bitrix с сохранением slug, настройка ЧПУ и 301, проверка и типичные ошибки.&lt;/p&gt;

&lt;p&gt;Сниппеты по статье&lt;/p&gt;

&lt;p&gt;Готовые проверенные фрагменты по шагам миграции:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WordPress: экспорт постов и SEO-меты для миграции (SQL) — выгрузка из wp_posts и wp_postmeta&lt;/li&gt;
&lt;li&gt;Bitrix: SEF и шаблон URL для блога — настройка ЧПУ с ELEMENT_CODE&lt;/li&gt;
&lt;li&gt;Nginx: 301 rewrite при смене структуры URL — постоянный редирект ветки адресов&lt;/li&gt;
&lt;li&gt;Bitrix: UrlRewriter — правило 301 редиректа — точечные редиректы через API&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;В чём проблема&lt;/p&gt;

&lt;p&gt;Типичные симптомы при кривой миграции: старые ссылки из поиска и закладок перестают открываться, в панелях вебмастеров растёт число ошибок доступа, трафик из органики падает.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❌ 404 на старых URL&lt;/li&gt;
&lt;li&gt;❌ Потеря meta title / description&lt;/li&gt;
&lt;li&gt;❌ Слетевшие canonical&lt;/li&gt;
&lt;li&gt;❌ Обнулённые позиции в Яндексе и Google&lt;/li&gt;
&lt;li&gt;❌ Пользователи не могут авторизоваться&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Подробнее про работу WordPress «под капотом» и настройку окружения — в статьях &lt;a href="https://viku-lov.ru/blog/wordpress-wp-cron-ne-vypolnyaetsya-nastroit-systemnyj-cron/" rel="noopener noreferrer"&gt;«WordPress: системный cron вместо WP-Cron»&lt;/a&gt; и &lt;a href="https://viku-lov.ru/blog/wordpress-dolgo-zagruzhaetsya-nginx-php-fpm-ttfb/" rel="noopener noreferrer"&gt;«WordPress: долгая загрузка, TTFB, nginx и PHP-FPM»&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;WordPress хранит данные в:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;wp_posts&lt;/li&gt;
&lt;li&gt;wp_users&lt;/li&gt;
&lt;li&gt;wp_usermeta&lt;/li&gt;
&lt;li&gt;wp_terms&lt;/li&gt;
&lt;li&gt;wp_term_relationships&lt;/li&gt;
&lt;li&gt;wp_postmeta&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bitrix работает через инфоблоки и таблицы:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;b_iblock_element&lt;/li&gt;
&lt;li&gt;b_iblock_section&lt;/li&gt;
&lt;li&gt;b_user&lt;/li&gt;
&lt;li&gt;b_utm_*&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Если просто «залить HTML» или перенести только текст без сохранения адресов и метаданных, поисковики перестают считать страницы теми же самыми: меняется URL — теряется история ссылок и поведенческие факторы. В итоге позиции обнуляются, трафик падает. Поэтому миграция без потери SEO — это всегда экспорт структуры (slug, meta, категории), настройка ЧПУ в Bitrix и обязательные 301-редиректы со старых адресов.&lt;/p&gt;




&lt;p&gt;Рабочее решение&lt;/p&gt;

&lt;p&gt;Миграция делится на 5 этапов: экспорт из WordPress, импорт в Bitrix, настройка URL и ЧПУ, настройка редиректов, перенос пользователей. Каждый этап можно выполнять на копии БД, чтобы не трогать прод до финальной проверки.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Экспорт данных из WordPress&lt;/li&gt;
&lt;li&gt;Импорт в Bitrix&lt;/li&gt;
&lt;li&gt;Сохранение структуры URL&lt;/li&gt;
&lt;li&gt;Настройка 301&lt;/li&gt;
&lt;li&gt;Перенос пользователей&lt;/li&gt;
&lt;/ol&gt;




&lt;ol&gt;
&lt;li&gt;Экспорт постов и страниц из WordPress&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Нужны посты и страницы из wp_posts и метаданные SEO из wp_postmeta (плагины вроде Yoast или Rank Math хранят там title, description и canonical). Прямой доступ к БД быстрее и надёжнее, чем выгрузка через админку. Оба запроса ниже можно выполнить в phpMyAdmin или из консоли MySQL; результат сохраняем в JSON для следующего шага. Готовый сниппет с пояснениями и ссылками на документацию: WordPress: экспорт постов и SEO-меты для миграции (SQL).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;
&lt;span class="c1"&gt;-- Посты и страницы&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_status&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;wp&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_posts&lt;/span&gt;

&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_type&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'post'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'page'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'publish'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Мета SEO (title, description, canonical)&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_value&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;wp&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_postmeta&lt;/span&gt;

&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_key&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\_&lt;/span&gt;&lt;span class="s1"&gt;yoast&lt;/span&gt;&lt;span class="se"&gt;\_&lt;/span&gt;&lt;span class="s1"&gt;wpseo&lt;/span&gt;&lt;span class="se"&gt;\_&lt;/span&gt;&lt;span class="s1"&gt;title'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\_&lt;/span&gt;&lt;span class="s1"&gt;yoast&lt;/span&gt;&lt;span class="se"&gt;\_&lt;/span&gt;&lt;span class="s1"&gt;wpseo&lt;/span&gt;&lt;span class="se"&gt;\_&lt;/span&gt;&lt;span class="s1"&gt;metadesc'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\_&lt;/span&gt;&lt;span class="s1"&gt;yoast&lt;/span&gt;&lt;span class="se"&gt;\_&lt;/span&gt;&lt;span class="s1"&gt;wpseo&lt;/span&gt;&lt;span class="se"&gt;\_&lt;/span&gt;&lt;span class="s1"&gt;canonical'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;ol&gt;
&lt;li&gt;Импорт в Bitrix через Python&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Пример скрипта (через REST или прямой PHP endpoint):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wp\_export.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;IBLOCK\_ID&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NAME&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;post\_title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;

&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CODE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;post\_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;# важно для URL
&lt;/span&gt;
&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ACTIVE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Y&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PREVIEW\_TEXT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;post\_content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;

&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://staging-site.ru/api/import.php&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;

&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ключевой момент — поле CODE в Bitrix должно совпадать с post_name из WordPress. Тогда адрес вида /blog/post-name/ останется тем же, и поисковики увидят тот же URL. Импорт можно делать через REST API Bitrix или через свой PHP-скрипт на временном endpoint; в примере выше используется один общий endpoint для всех записей.&lt;/p&gt;




&lt;ol&gt;
&lt;li&gt;Сохранение структуры URL&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;В WordPress адреса часто выглядят как /blog/post-name/ или /category/design/. В Bitrix нужно явно включить ЧПУ (SEF) и задать шаблон так, чтобы символьный код элемента подставлялся в URL. Создаём раздел с псевдонимом blog, в настройках сайта включаем SEF и указываем папку, например /blog/. Шаблон детальной страницы — #ELEMENT_CODE#/, тогда адрес будет /blog/имя-element-code/. В компоненте списка новостей (например, news) задаём (подробнее — в сниппете Bitrix: SEF и шаблон URL для блога):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;
&lt;span class="s2"&gt;"SEF\_MODE"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"Y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="s2"&gt;"SEF\_FOLDER"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"/blog/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="s2"&gt;"SEF\_URL\_TEMPLATES"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;

&lt;span class="s2"&gt;"detail"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"#ELEMENT\_CODE#/"&lt;/span&gt;

&lt;span class="p"&gt;]&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;ol&gt;
&lt;li&gt;301 редиректы&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Если часть URL изменилась (например, был /category/, стал /blog/), все старые адреса должны отдавать 301 с новым Location. Иначе поисковики будут считать страницы удалёнными или дублями. Редиректы можно настроить в nginx для массовых правил (один rewrite на целую ветку старых URL) или в Bitrix через UrlRewriter для точечных перенаправлений. Готовые сниппеты: Nginx: 301 rewrite при смене структуры URL, Bitrix: UrlRewriter — правило 301 редиректа. Базовые приёмы nginx — в &lt;a href="https://viku-lov.ru/blog/nginx-basics-web-server-fundamentals-2026/" rel="noopener noreferrer"&gt;«Nginx: основы веб-сервера»&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;
&lt;span class="k"&gt;rewrite&lt;/span&gt; &lt;span class="s"&gt;^/old-category/(.&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;*)&lt;/span&gt;$ &lt;span class="n"&gt;/blog/&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="s"&gt;permanent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Или массово через таблицу редиректов Bitrix (модуль «Управление структурой» или API UrlRewriter в том же проекте, где настраивали SEF):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Bitrix\Main\UrlRewriter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;UrlRewriter&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;

&lt;span class="s2"&gt;"CONDITION"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"#^/old-url/$#"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="s2"&gt;"RULE"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="s2"&gt;"ID"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="s2"&gt;"PATH"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"/new-url/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;ol&gt;
&lt;li&gt;Перенос пользователей&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Из WordPress: SELECT ID, user_login, user_email, user_pass FROM wp_users;. В Bitrix создаём пользователей так:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;
&lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CUser&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$arFields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;

&lt;span class="s2"&gt;"LOGIN"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$wpUser&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"user\_login"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;

&lt;span class="s2"&gt;"EMAIL"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$wpUser&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"user\_email"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;

&lt;span class="s2"&gt;"PASSWORD"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bin2hex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;random\_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;

&lt;span class="s2"&gt;"ACTIVE"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"Y"&lt;/span&gt;

&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$arFields&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Пароли из WordPress (хеш в user_pass) нельзя подставить в Bitrix: алгоритмы и соль разные. Поэтому при переносе пользователей задаём временный пароль (в коде выше — случайная строка) и при первом входе отправляем пользователю сброс пароля по email либо даём инструкцию сменить пароль в личном кабинете. Так вы сохраните учётки и доступ без взлома старых хешей.&lt;/p&gt;




&lt;p&gt;Проверка результата (диагностика)&lt;/p&gt;

&lt;p&gt;Диагностика после миграции сводится к трём проверкам: новый URL отдаёт 200, старые адреса отдают 301 с правильным Location, в sitemap перечислены те же URL, что и раньше (или новые, если вы сознательно сменили структуру и настроили редиректы). Команды ниже выполняйте с подставленным вашим доменом.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
URL страницы — ожидаем HTTP/1.1 200 OK

curl &lt;span class="nt"&gt;-I&lt;/span&gt; https://site.ru/blog/post-name/

Старый URL — ожидаем 301 Moved Permanently и Location: https://site.ru/new-url/

curl &lt;span class="nt"&gt;-I&lt;/span&gt; https://site.ru/old-url/

Sitemap: URL в файле должны совпадать со старыми

curl https://site.ru/sitemap.xml

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Проверка индексации&lt;/p&gt;

&lt;p&gt;После переключения прод-домена на Bitrix в Яндекс.Вебмастер и Google Search Console нужно заново отправить sitemap и при необходимости запросить переобход важных URL. Раздел «Покрытие» (coverage) покажет, какие страницы в индексе и нет ли массовых 404. Первые дни возможны временные просадки; если 301 настроены и URL совпадают, через 1–2 недели индекс стабилизируется.&lt;/p&gt;




&lt;p&gt;Если не работает&lt;/p&gt;

&lt;p&gt;Ниже — частые причины сбоев и что проверить в первую очередь. Если после миграции часть страниц отдаёт 404, трафик падает или пользователи не могут войти — пройдите по пунктам ниже и сверьтесь с разделом «Проверка результата (диагностика)». Чаще всего проблема в пропущенном этапе: забыли мету, поменяли slug или не настроили редиректы. Проверка по чек-листу выше обычно выявляет причину.&lt;/p&gt;




&lt;p&gt;Типичные ошибки&lt;/p&gt;

&lt;p&gt;❌ Не перенесли meta description&lt;/p&gt;

&lt;p&gt;Причина: выгрузили только wp_posts и не учли wp_postmeta, где лежат title, description и canonical от Yoast/Rank Math. Решение: выгрузить мету (запрос выше) и при импорте в Bitrix записывать её в отдельные свойства инфоблока или в отдельные поля; в шаблоне детальной страницы выводить эти поля в &lt;/p&gt; и мета-тегах.



&lt;p&gt;❌ Изменили структуру URL&lt;/p&gt;

&lt;p&gt;Причина: в Bitrix включили SEF, но символьный код элемента (CODE) задали произвольно или из названия, а не из post_name WordPress. В итоге адреса отличаются, старые ссылки ведут в никуда. Решение: при импорте строго задавать CODE = post_name для каждой записи и проверять по списку, что URL совпадают.&lt;/p&gt;




&lt;p&gt;❌ Не сделали 301&lt;/p&gt;

&lt;p&gt;Причина: надеялись, что поисковик «переиндексирует» или что достаточно обновить sitemap. Без 301 старые URL считаются мёртвыми, ссылочный вес и поведенческие факторы не передаются на новый адрес. Решение: для каждого старого адреса настроить 301 на новый (nginx или UrlRewriter в Bitrix), затем проверить через curl -I.&lt;/p&gt;




&lt;p&gt;❌ Потеряли теги&lt;/p&gt;

&lt;p&gt;Причина: в WordPress теги и категории хранятся в wp_terms и wp_term_relationships; если их не выгрузить и не завести в Bitrix (разделы инфоблока или HighloadBlock), на новом сайте пропадут рубрики и теги. Решение: экспортировать термины и связи, при импорте создавать разделы или записи в HighloadBlock и привязывать элементы к ним.&lt;/p&gt;




&lt;p&gt;Где применять&lt;/p&gt;

&lt;p&gt;Подход подходит для продакшена при полной смене CMS с WordPress на Bitrix, в том числе в Docker или на &lt;a href="https://viku-lov.ru/blog/bitrixvm-centos-ssl-letsencrypt-create-renew/" rel="noopener noreferrer"&gt;BitrixVM&lt;/a&gt; (после переноса имеет смысл проверить SSL и при необходимости обновить сертификаты). Особенно важно сохранять URL и 301 для проектов с существенным SEO-трафиком и для интернет-магазинов, у которых блог или статьи переносятся в Bitrix отдельно от каталога. Перед выкатом на прод стоит прогнать все шаги на копии сайта и убедиться, что количество записей, разделов и пользователей совпадает с выгрузкой из WordPress.&lt;/p&gt;




&lt;p&gt;Итог&lt;/p&gt;

&lt;p&gt;Миграция с WordPress на Bitrix — это не «экспорт-импорт» одной кнопкой, а последовательность: выгрузка постов и меты, импорт с сохранением символьного кода, настройка ЧПУ, 301 и проверка. Если сохранить slug, структуру URL, meta-поля, настроить 301 и актуальный sitemap, позиции не падают. Если хоть один пункт пропустить — получите просадку трафика на 30–70% в первые две недели. SEO здесь сводится к структуре, предсказуемым URL и корректным редиректам: нарушил — заплатил трафиком.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://viku-lov.ru/blog/migraciya-s-wordpress-na-bitrix-bez-poteri-seo" rel="noopener noreferrer"&gt;Read more on viku-lov.ru&lt;/a&gt;&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>bitrix</category>
      <category>seo</category>
      <category>migration</category>
    </item>
    <item>
      <title>Почему миграции с WordPress на Bitrix рушат SEO</title>
      <dc:creator>Андрей Викулов (VProger)</dc:creator>
      <pubDate>Mon, 23 Feb 2026 18:59:44 +0000</pubDate>
      <link>https://forem.com/_vproger_/pochiemu-mighratsii-s-wordpress-na-bitrix-rushat-seo-12k7</link>
      <guid>https://forem.com/_vproger_/pochiemu-mighratsii-s-wordpress-na-bitrix-rushat-seo-12k7</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6gep90r59bmaup7efv4p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6gep90r59bmaup7efv4p.png" alt="Почему миграции с WordPress на Bitrix рушат SEO" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Почему миграции с WordPress на Bitrix рушат SEO&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Коротко:&lt;/strong&gt; SEO падает не из-за смены CMS. Оно падает из-за неправильной архитектуры миграции и игнорирования структуры URL.&lt;/p&gt;

&lt;p&gt;За последние годы я участвовал в нескольких переносах проектов с WordPress на Bitrix с трафиком от 50 000 до 300 000 визитов в месяц. В 80% случаев просадка происходила не из-за «плохого Bitrix», а из-за управленческих и архитектурных ошибок. В оставшихся 20% причиной оказывались технические просчёты: не те редиректы, смена адресов «для красоты» или перенос только контента без метаданных. Ниже — о том, в чём именно спор, что происходит в реальности и при каких условиях миграция не бьёт по трафику.&lt;/p&gt;

&lt;p&gt;В чём спор&lt;/p&gt;

&lt;p&gt;Обычно говорят:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;«CMS не влияет на SEO»&lt;/li&gt;
&lt;li&gt;«Главное — контент, поисковик разберётся»&lt;/li&gt;
&lt;li&gt;«Редиректы можно сделать потом»&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Формально доля правды в этом есть: сама по себе система управления контентом не ранжируется. Но то, что из неё следует — URL-модель, структура разделов, скорость ответа сервера и поведение при переезде — уже влияет на индексацию и ранжирование. Поисковые системы реагируют не на название CMS, а на то, остались ли прежними адреса страниц, не посылаете ли вы пользователей и роботов на 404 и не подменили ли вы один документ другим без явного сигнала (301). Игнорировать это — значит заранее закладывать просадку. То же касается тезиса «редиректы потом»: после переключения домена старые URL начинают отдавать 404, ссылочный вес и поведенческие факторы теряются, и «доделать потом» уже не восстанавливает прежнюю картину в глазах поисковика.&lt;/p&gt;

&lt;p&gt;Что происходит в реальности&lt;/p&gt;

&lt;p&gt;При типичной миграции с WordPress на Bitrix я сталкиваюсь с одними и теми же проблемами:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Меняется структура URL: было «/blog/post-name/», стало «/news/123/» или «/katalog/stati/post-name/»&lt;/li&gt;
&lt;li&gt;Теряются slug: в WordPress адрес задаётся полем post_name, в Bitrix — полем CODE; при некорректном маппинге адреса расходятся&lt;/li&gt;
&lt;li&gt;Мета-поля не переносятся: title, description и canonical остаются в старой базе или подставляются шаблоном по умолчанию&lt;/li&gt;
&lt;li&gt;Не настроены 301-редиректы: старые ссылки из выдачи и с других сайтов оказываются битыми или ведут на временный редирект (302), что не передаёт вес&lt;/li&gt;
&lt;li&gt;Sitemap публикуется с задержкой или содержит уже неактуальные URL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;В результате поисковик фиксирует: часть страниц пропала, часть появилась по новым адресам, часть ссылок ведёт на 404, canonical изменился. Он не «понимает», что вы переехали; он видит разрыв целостности сайта и переоценивает его. Типичный пример из практики: проект с примерно 120 000 визитов в месяц после «технического переноса» без сохранения slug через три недели снизил трафик примерно до 47 000. Причина — массовые 404 на старых URL и частичные редиректы вместо сквозных 301. В другом кейсе заказчик настаивал на смене структуры каталога «под новую логику»; через два месяца органический трафик упал почти вдвое, и возврат к старой схеме URL с 301 уже не вернул позиции в прежний вид — пришлось выводить сайт из просадки месяцами. Оба случая объединяет одно: решение о миграции принималось без жёсткого требования сохранить адреса и без плана по редиректам.&lt;/p&gt;

&lt;p&gt;Когда миграция не убивает SEO&lt;/p&gt;

&lt;p&gt;Перенос безопасен для трафика, если соблюдены условия:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Slug полностью совпадает со старым: адрес страницы после переноса тот же, что и в WordPress&lt;/li&gt;
&lt;li&gt;Структура вложенности не изменилась: если были разделы и подразделы, они сохраняются в том же виде&lt;/li&gt;
&lt;li&gt;Meta title и description перенесены и отображаются в выдаче так же, как раньше (или осознанно улучшены)&lt;/li&gt;
&lt;li&gt;Все старые URL отдают 301 permanent на новый адрес, без 302 и без 404&lt;/li&gt;
&lt;li&gt;Sitemap обновлён в день релиза и содержит актуальные адреса&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;В такой постановке CMS вторична: поисковику важна стабильная структура и предсказуемое поведение. Структура и дисциплина первичны; платформа — лишь инструмент. Отсюда практический вывод: перед переездом нужен чёткий чек-лист (выгрузка всех URL, маппинг slug → CODE, перенос мета-полей, настройка 301, обновление sitemap и проверка на staging), и ни один пункт нельзя откладывать «на потом».&lt;/p&gt;

&lt;p&gt;Когда я бы не стал переносить проект&lt;/p&gt;

&lt;p&gt;Я бы отложил или пересмотрел миграцию в таких случаях:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Если 80% трафика — органика, а бизнес не готов к просадке на 1–2 месяца. Восстановление индекса и доверие после сбоев занимает время; это нужно закладывать в планы&lt;/li&gt;
&lt;li&gt;Если нет staging или тестового окружения для полной проверки до переключения прод-домена&lt;/li&gt;
&lt;li&gt;Если нет выгрузки всех URL старого сайта: без полного списка адресов невозможно настроить 301 и проверить покрытие&lt;/li&gt;
&lt;li&gt;Если команда путает 302 и 301 или не понимает разницы между временным и постоянным редиректом&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Миграция с сохранением SEO — это инфраструктурная операция с чёткими критериями приёмки, а не просто «перевёрстка» или «перенос контента». Руководству и заказчику важно заранее принять возможность временной просадки даже при идеальной технике (переобход индекса, обновление сниппетов) и не давить на команду «сделать быстрее» в ущерб проверкам. Сокращение сроков за счёт пропуска этапов почти всегда оборачивается многомесячным восстановлением трафика.&lt;/p&gt;

&lt;p&gt;Технический аспект&lt;/p&gt;

&lt;p&gt;Без эмоций, только факты:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;URL-модель:&lt;/strong&gt; в WordPress slug хранится в поле post_name, в Bitrix — в поле CODE элемента инфоблока. Ошибка в маппинге при переносе ломает адреса и делает старые ссылки нерабочими&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SEO-поля:&lt;/strong&gt; в WordPress метаданные страниц часто лежат в wp_postmeta (в том числе при использовании Yoast, Rank Math и аналогов), в Bitrix — в свойствах инфоблока или отдельных полях; перенос должен быть явно спроектирован&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Редиректы:&lt;/strong&gt; эффективнее и надёжнее настраивать на уровне веб-сервера (nginx или аналог), а не только средствами CMS, особенно при массовых правилах&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Производительность:&lt;/strong&gt; «голый» Bitrix без кеша и оптимизаций обычно тяжелее типового WordPress; это может отразиться на Core Web Vitals и косвенно на ранжировании&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Проверка после миграции сводится к простым шагам: убедиться, что запрос к старому URL возвращает ответ 301 с заголовком Location на новый адрес, а запрос к новому URL — ответ 200. Если вместо этого вы видите 404 или 302, позиции и трафик будут падать, пока ситуация не исправлена. Имеет смысл заранее подготовить список ключевых страниц (топ по трафику и по ссылочному весу) и после переключения пройтись по нему вручную или с помощью скриптов, проверяя статус-коды и заголовки. То же касается sitemap и раздела «Покрытие» в панелях вебмастеров: расхождения между старым и новым списком URL нужно устранять сразу, а не «в следующем спринте».&lt;/p&gt;

&lt;p&gt;Типичные ошибки при принятии решения&lt;/p&gt;

&lt;p&gt;Чаще всего встречаются такие просчёты:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Переносят только HTML-контент, не заботясь о slug, мета-тегах и редиректах&lt;/li&gt;
&lt;li&gt;Меняют структуру URL «чтобы стало красивее» или «логичнее», не оценивая последствия для индекса&lt;/li&gt;
&lt;li&gt;Игнорируют категории и теги: в WordPress они живут в своей модели данных, в Bitrix их нужно явно смоделировать (разделы, Highload и т.п.)&lt;/li&gt;
&lt;li&gt;Забывают снять запрет индексации или открыть сайт для роботов после переноса&lt;/li&gt;
&lt;li&gt;Не проверяют robots.txt и настройки в панелях вебмастеров после переключения&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Самая дорогая ошибка — считать, что SEO восстановится само собой. Не восстановится: поисковику нужны однозначные сигналы (те же URL или корректные 301), актуальный sitemap и стабильная работа сайта. Не менее опасно поручать миграцию команде, которая не различает постоянный и временный редирект или не умеет выгружать полный список URL из старой CMS: без этого любые обещания «сохранить SEO» остаются декларацией. Имеет смысл зафиксировать критерии приёмки до старта работ и привязывать этапы оплаты или релиза к их выполнению.&lt;/p&gt;

&lt;p&gt;Моя позиция&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SEO рушит не Bitrix и не WordPress. Его рушит небрежная миграция.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Если при переносе сохраняется структура URL, корректно настроены 301, перенесены метаданные и проведено тестирование на staging до выката на прод — трафик остаётся стабильным. Если хотя бы один из этих пунктов пропущен — просадка практически гарантирована.&lt;/p&gt;

&lt;p&gt;CMS — инструмент. Структура и дисциплина — основа. Понимание этого и отличает миграцию, после которой сайт продолжает приносить трафик, от той, после которой месяцами разгребают последствия. Резюмируя: винить Bitrix или WordPress в просадке трафика после переезда — значит смотреть не туда. Смотреть нужно на то, сохранили ли вы адреса и сигналы для поисковика; если да — смена платформы остаётся прозрачной и для пользователей, и для роботов. Если нет — просадка почти неизбежна, и исправлять придётся уже по факту, с потерями во времени и в деньгах.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://viku-lov.ru/blog/pochemu-migracii-s-wordpress-na-bitrix-rushat-seo" rel="noopener noreferrer"&gt;Read more on viku-lov.ru&lt;/a&gt;&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>bitrix</category>
      <category>seo</category>
      <category>migration</category>
    </item>
    <item>
      <title>WordPress: JSON-LD для CPT — дубли и ошибки</title>
      <dc:creator>Андрей Викулов (VProger)</dc:creator>
      <pubDate>Mon, 23 Feb 2026 18:56:45 +0000</pubDate>
      <link>https://forem.com/_vproger_/wordpress-json-ld-dlia-cpt-dubli-i-oshibki-181j</link>
      <guid>https://forem.com/_vproger_/wordpress-json-ld-dlia-cpt-dubli-i-oshibki-181j</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2k0u86yn3simfkwad4u5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2k0u86yn3simfkwad4u5.png" alt="WordPress: JSON-LD для CPT — дубли и ошибки" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;WordPress: JSON-LD для CPT — дубли и ошибки&lt;/p&gt;

&lt;h1&gt;
  
  
  WordPress: JSON-LD для CPT — дубли и ошибки
&lt;/h1&gt;

&lt;p&gt;Владельцы сайтов на WordPress часто ловят одну и ту же боль: добавили Custom Post Type (например, &lt;code&gt;articles&lt;/code&gt; или &lt;code&gt;news&lt;/code&gt;), а JSON-LD разметка либо &lt;strong&gt;не появляется&lt;/strong&gt;, либо &lt;strong&gt;ломается синтаксисом&lt;/strong&gt;, либо &lt;strong&gt;дублируется в &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;&lt;/strong&gt;. В итоге Google Search Console/ Rich Results Test ругаются на невалидный Structured Data — и красивые сниппеты в выдаче не прилетают.&lt;/p&gt;

&lt;p&gt;Ниже — рабочий вариант генерации &lt;code&gt;BlogPosting&lt;/code&gt;/&lt;code&gt;Article&lt;/code&gt; через &lt;code&gt;wp_head&lt;/code&gt;, с безопасной сериализацией JSON (&lt;code&gt;wp_json_encode&lt;/code&gt;) и защитой от дублей.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Сниппеты по статье:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JSON-LD класс для CPT&lt;/li&gt;
&lt;li&gt;JSON-LD для CPT без класса&lt;/li&gt;
&lt;li&gt;curl: проверка JSON-LD в head&lt;/li&gt;
&lt;li&gt;Диагностика JSON-LD для CPT&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  В чём проблема
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Реальный симптом
&lt;/h3&gt;

&lt;p&gt;Вы регистрируете Custom Post Type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;register_post_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'articles'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s1"&gt;'public'&lt;/span&gt;      &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'has_archive'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Но на странице &lt;code&gt;/articles/my-post/&lt;/code&gt; происходит одно из трёх:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;В исходнике нет блока &lt;code&gt;&amp;lt;script type="application/ld+json"&amp;gt;…&amp;lt;/script&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;JSON ломается (в консоли/валидаторе): &lt;code&gt;Unexpected token &amp;lt; in JSON&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Разметка дублируется: 2–3 одинаковых блока в &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Пример из Rich Results Test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ERROR: Missing required field "author"
ERROR: Missing required field "datePublished"
WARNING: Multiple BlogPosting detected on page
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Почему это происходит
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Тема не генерирует JSON-LD для CPT&lt;/strong&gt; — часто разметка делается только для стандартного &lt;code&gt;post&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SEO-плагин добавляет свою разметку&lt;/strong&gt; — и вы “добавили ещё одну такую же”.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Неправильный хук&lt;/strong&gt; — вставили в &lt;code&gt;template_redirect&lt;/code&gt;/шаблон, а не в &lt;code&gt;wp_head&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JSON собирается руками строкой&lt;/strong&gt; — кавычки, спецсимволы, HTML в excerpt → привет, сломанный JSON. Нужен &lt;code&gt;wp_json_encode()&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Рабочее решение
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Шаг 1: Класс генерации JSON-LD для CPT
&lt;/h3&gt;

&lt;p&gt;Создайте файл:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;wp-content/themes/your-theme/includes/class-jsonld-cpt.php&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(или в небольшом плагине — логика та же).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="cd"&gt;/**
 * JSON-LD Generator for Custom Post Types
 * Place in:
 * - wp-content/themes/your-theme/includes/class-jsonld-cpt.php
 * Or:
 * - wp-content/plugins/your-plugin/includes/class-jsonld-cpt.php
 */&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;JSONLD_CPT_Generator&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$target_post_types&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'articles'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'news'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'publications'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'wp_head'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'output_jsonld'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;output_jsonld&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Только single страницы нужных CPT&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;is_singular&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;target_post_types&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$post&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="n"&gt;WP_Post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Защита от дублей: если уже выводили — выходим&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;did_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'jsonld_cpt_output'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$jsonld&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;build_blogposting_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$jsonld&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;script type="application/ld+json"&amp;gt;'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nf"&gt;wp_json_encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$jsonld&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;JSON_UNESCAPED_UNICODE&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="no"&gt;JSON_PRETTY_PRINT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;/script&amp;gt;'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// Маркер, что мы уже вывели JSON-LD&lt;/span&gt;
        &lt;span class="nf"&gt;do_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'jsonld_cpt_output'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;build_blogposting_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;WP_Post&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$author_id&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;post_author&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$author_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_the_author_meta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'display_name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$author_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$author_url&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_author_posts_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$author_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$publish_date&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_the_date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;DATE_W3C&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$modified_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_the_modified_date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;DATE_W3C&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Изображение: featured image или заглушка&lt;/span&gt;
        &lt;span class="nv"&gt;$image_id&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;get_post_thumbnail_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="no"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$image_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$image_id&lt;/span&gt;
            &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;wp_get_attachment_image_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$image_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'full'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;get_stylesheet_directory_uri&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/assets/images/og-default.png'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Категория (первая)&lt;/span&gt;
        &lt;span class="nv"&gt;$categories&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_the_terms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="no"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'category'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$section&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$categories&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;is_wp_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$categories&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nv"&gt;$categories&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
            &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'General'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="nv"&gt;$schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'@context'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'https://schema.org'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'@type'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'BlogPosting'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'mainEntityOfPage'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'@type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'WebPage'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'@id'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;get_permalink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="no"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="s1"&gt;'headline'&lt;/span&gt;      &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;get_the_title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="no"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'description'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;get_the_excerpt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="no"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'image'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'@type'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'ImageObject'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'url'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$image_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'width'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'height'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;630&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="s1"&gt;'datePublished'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$publish_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'dateModified'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$modified_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'author'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'@type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Person'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'name'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$author_name&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="nf"&gt;get_bloginfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="s1"&gt;'url'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$author_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="s1"&gt;'publisher'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'@type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Organization'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'name'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;get_bloginfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="s1"&gt;'logo'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                    &lt;span class="s1"&gt;'@type'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'ImageObject'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s1"&gt;'url'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;get_stylesheet_directory_uri&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/assets/images/logo.png'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s1"&gt;'width'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s1"&gt;'height'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="s1"&gt;'articleSection'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$section&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'wordCount'&lt;/span&gt;      &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str_word_count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;wp_strip_all_tags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;post_content&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;

        &lt;span class="c1"&gt;// keywords из тегов&lt;/span&gt;
        &lt;span class="nv"&gt;$tags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_the_terms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="no"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'post_tag'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$tags&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;is_wp_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$tags&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$schema&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'keywords'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;implode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;', '&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;wp_list_pluck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$tags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$schema&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Инициализация&lt;/span&gt;
&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JSONLD_CPT_Generator&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Шаг 2: Подключение в &lt;code&gt;functions.php&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// wp-content/themes/your-theme/functions.php&lt;/span&gt;

&lt;span class="k"&gt;require_once&lt;/span&gt; &lt;span class="nf"&gt;get_stylesheet_directory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/includes/class-jsonld-cpt.php'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Шаг 3: Альтернатива без класса (быстро и грубо, но работает)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'wp_head'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'custom_cpt_jsonld_output'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;custom_cpt_jsonld_output&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$target_types&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'articles'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'news'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;is_singular&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$target_types&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;did_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'jsonld_cpt_output'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$post&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="n"&gt;WP_Post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nv"&gt;$schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'@context'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'https://schema.org'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'@type'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'BlogPosting'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'headline'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;get_the_title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="no"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s1"&gt;'datePublished'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;get_the_date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;DATE_W3C&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s1"&gt;'dateModified'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;get_the_modified_date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;DATE_W3C&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s1"&gt;'author'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'@type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Person'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'name'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;get_the_author_meta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'display_name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;post_author&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="s1"&gt;'publisher'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'@type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Organization'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'name'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;get_bloginfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;script type="application/ld+json"&amp;gt;'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nf"&gt;wp_json_encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;JSON_UNESCAPED_UNICODE&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="no"&gt;JSON_PRETTY_PRINT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;/script&amp;gt;'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nf"&gt;do_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'jsonld_cpt_output'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Проверка результата
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1) Проверка в исходнике
&lt;/h3&gt;

&lt;p&gt;Откройте страницу CPT и найдите в &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;должен быть &lt;strong&gt;один&lt;/strong&gt; блок &lt;code&gt;&amp;lt;script type="application/ld+json"&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;внутри — валидный JSON (без HTML, без “поехавших” кавычек)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2) Быстрая проверка через &lt;code&gt;curl&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://yoursite.com/articles/my-post/ | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-A&lt;/span&gt; 60 &lt;span class="s1"&gt;'application/ld+json'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3) Проверка на дубли
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://yoursite.com/articles/my-post/ | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'application/ld+json'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ожидаемо: &lt;code&gt;1&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4) Google Rich Results Test
&lt;/h3&gt;

&lt;p&gt;Вбейте URL в Rich Results Test и смотрите:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;code&gt;BlogPosting&lt;/code&gt;/&lt;code&gt;Article detected&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;✅ критических ошибок нет&lt;/li&gt;
&lt;li&gt;⚠️ warnings допустимы (например, &lt;code&gt;articleBody&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Типичные ошибки
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ❌ Ошибка 1: JSON ломается из-за кавычек в заголовке
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Симптом:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Uncaught SyntaxError: Unexpected token " in JSON
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Причина:&lt;/strong&gt; JSON собирается строкой или через &lt;code&gt;json_encode&lt;/code&gt; без нормальной сериализации.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Исправление:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ плохо&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{"headline":"'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nf"&gt;get_the_title&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'"}'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ нормально&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nf"&gt;wp_json_encode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'headline'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;get_the_title&lt;/span&gt;&lt;span class="p"&gt;()]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  ❌ Ошибка 2: Разметка дублируется
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Симптом:&lt;/strong&gt; 2–3 блока JSON-LD в &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Причина:&lt;/strong&gt; тема + SEO-плагин (Yoast/RankMath) или несколько &lt;code&gt;add_action('wp_head', ...)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Исправление:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;выключить structured data для CPT в SEO-плагине (если есть такая настройка),&lt;/li&gt;
&lt;li&gt;оставить один источник разметки,&lt;/li&gt;
&lt;li&gt;использовать маркер &lt;code&gt;did_action('jsonld_cpt_output')&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  ❌ Ошибка 3: &lt;code&gt;Missing required field "author"&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Причина:&lt;/strong&gt; неверная структура &lt;code&gt;author&lt;/code&gt; или он пустой.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Исправление (правильная структура):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="s1"&gt;'author'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s1"&gt;'@type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Person'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'name'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;get_the_author_meta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'display_name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;post_author&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="s1"&gt;'url'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;get_author_posts_url&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;post_author&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  ❌ Ошибка 4: JSON-LD не появляется на CPT
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Причина:&lt;/strong&gt; &lt;code&gt;is_singular()&lt;/code&gt; проверяет не тот post type (или CPT другой).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Исправление:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$target_types&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'articles'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'news'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'publications'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;is_singular&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$target_types&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Если не работает: чеклист
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;CPT реально существует:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;post_type_exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'articles'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// должно вернуть true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Хук вообще вызывается (временно):
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nb"&gt;error_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'JSONLD: output called'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Кэш мешает:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;очистить кэш плагина/сервера,&lt;/li&gt;
&lt;li&gt;отключить page cache на время проверки.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Посмотреть PHP-логи:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/php/error.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Где применять
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Среда&lt;/th&gt;
&lt;th&gt;Применимость&lt;/th&gt;
&lt;th&gt;Примечание&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Production&lt;/td&gt;
&lt;td&gt;✅ Да&lt;/td&gt;
&lt;td&gt;основной сценарий&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dev/Staging&lt;/td&gt;
&lt;td&gt;✅ Да&lt;/td&gt;
&lt;td&gt;тест перед деплоем&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker&lt;/td&gt;
&lt;td&gt;✅ Да&lt;/td&gt;
&lt;td&gt;без изменений&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BitrixVM&lt;/td&gt;
&lt;td&gt;✅ Да&lt;/td&gt;
&lt;td&gt;только проверь права на логи&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nginx/Apache&lt;/td&gt;
&lt;td&gt;✅ Да&lt;/td&gt;
&lt;td&gt;на генерацию не влияет&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CI/CD&lt;/td&gt;
&lt;td&gt;⚠️ Частично&lt;/td&gt;
&lt;td&gt;удобно валидировать через CLI&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Связанные материалы
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Сниппеты по статье:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JSON-LD класс для CPT&lt;/li&gt;
&lt;li&gt;JSON-LD для CPT без класса&lt;/li&gt;
&lt;li&gt;curl: проверка JSON-LD в head&lt;/li&gt;
&lt;li&gt;Диагностика JSON-LD для CPT&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Другие сниппеты и справочник:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CLI валидатор JSON-LD&lt;/li&gt;
&lt;li&gt;CLI валидатор микроразметки&lt;/li&gt;
&lt;li&gt;Термин: JSON-LD&lt;/li&gt;
&lt;li&gt;Термин: Schema.org&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Итоги
&lt;/h2&gt;

&lt;p&gt;Если JSON-LD для CPT в WordPress &lt;strong&gt;не появляется&lt;/strong&gt;, &lt;strong&gt;ломается&lt;/strong&gt; или &lt;strong&gt;дублируется&lt;/strong&gt;, почти всегда виноваты: неправильный хук, ручная сборка JSON и конфликт с SEO-плагином. Решение простое и скучное (значит хорошее): &lt;code&gt;wp_head&lt;/code&gt; + &lt;code&gt;wp_json_encode()&lt;/code&gt; + маркер от дублей через &lt;code&gt;did_action()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Read more on viku-lov.ru: &lt;a href="https://viku-lov.ru/blog/wordpress-json-ld-blogposting-custom-post-type" rel="noopener noreferrer"&gt;https://viku-lov.ru/blog/wordpress-json-ld-blogposting-custom-post-type&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://viku-lov.ru/blog/wordpress-json-ld-blogposting-custom-post-type" rel="noopener noreferrer"&gt;Read more on viku-lov.ru&lt;/a&gt;&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>jsonld</category>
      <category>customposttype</category>
      <category>schemaorg</category>
    </item>
    <item>
      <title>Как включить GeoIP в Nginx: переменные для логов и заголовков</title>
      <dc:creator>Андрей Викулов (VProger)</dc:creator>
      <pubDate>Mon, 23 Feb 2026 18:53:18 +0000</pubDate>
      <link>https://forem.com/_vproger_/kak-vkliuchit-geoip-v-nginx-pieriemiennyie-dlia-loghov-i-zagholovkov-46bc</link>
      <guid>https://forem.com/_vproger_/kak-vkliuchit-geoip-v-nginx-pieriemiennyie-dlia-loghov-i-zagholovkov-46bc</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvkc4an7jgjmeb0uaqrt7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvkc4an7jgjmeb0uaqrt7.png" alt="Как включить GeoIP в Nginx: переменные для логов и заголовков" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Как включить GeoIP в Nginx: переменные для логов и заголовков&lt;/p&gt;

&lt;p&gt;Запрос «как включить GeoIP в Nginx и получить переменные для логов и заголовков» чаще всего всплывает у тех, кто ведёт аудит трафика на VPS: нужно видеть страну/город в access.log или прокинуть страну в приложение (например, Bitrix за Apache). В конце будет рабочая конфигурация для BitrixVM (nginx+httpd), проверка, и список типичных проблем.&lt;/p&gt;




&lt;p&gt;В чём проблема&lt;/p&gt;

&lt;p&gt;Симптом обычно один из этих:&lt;/p&gt;

&lt;p&gt;1) В конфиг добавили geoip_country / geoip_city, а Nginx на перезагрузке падает:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
nginx: [emerg] unknown directive "geoip\_country" in /etc/nginx/nginx.conf:XX

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Или модуль подключили, но переменные пустые — в логах вместо страны -:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
1.2.3.4 - - [16/Feb/2026:12:10:11 +0000] "GET / HTTP/2.0" 200 123 "-" "-" country=- city=-

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Причины ровно две:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Модуль не загружен&lt;/strong&gt; (динамические модули подключаются через load_module, иначе директивы неизвестны). Официальный модуль называется ngx_http_geoip_module и создаёт переменные на основе баз MaxMind. (&lt;a href="https://nginx.org/en/docs/http/ngx_http_geoip_module.html" rel="noopener noreferrer"&gt;nginx.org&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Нет базы&lt;/strong&gt; или она не в том формате. Важно: legacy GeoLite (.dat) давно сняты с раздачи, MaxMind официально прекратил GeoLite Legacy. Поэтому старые команды с GeoIP.dat.gz по прямым ссылкам сегодня чаще мёртвые, чем живые. (&lt;a href="https://blog.maxmind.com/2022/06/geoip-legacy-databases-have-been-retired" rel="noopener noreferrer"&gt;MaxMind&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ещё по современности (2025–2026): если вам нужна актуальная бесплатная геобаза, &lt;strong&gt;это GeoLite2 (MMDB)&lt;/strong&gt; и по сути это уже &lt;strong&gt;модуль GeoIP2&lt;/strong&gt; (в NGINX Plus он официальный). (&lt;a href="https://docs.nginx.com/nginx-admin-guide/dynamic-modules/geoip/" rel="noopener noreferrer"&gt;docs.nginx.com&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Но в BitrixVM обычно стоит open source Nginx — там штатный модуль ngx_http_geoip_module работает с legacy-форматом. Поэтому ниже — два практичных пути: (A) включить legacy GeoIP модуль с .dat, если база у вас есть (коммерческая/внутренняя), (B) если у вас NGINX Plus — включить GeoIP2 как правильный вариант на будущее.&lt;/p&gt;




&lt;p&gt;Рабочее решение&lt;/p&gt;

&lt;p&gt;Ниже шаги ориентированы на BitrixVM 9+ (CentOS Stream 9+, nginx + httpd). Все команды выполняются &lt;strong&gt;из-под root&lt;/strong&gt; , sudo не используем.&lt;/p&gt;

&lt;p&gt;1) Убедиться, что Nginx установлен из официального репозитория&lt;/p&gt;

&lt;p&gt;Официальные пакеты Nginx ставятся из репозитория nginx.org. Инструкция по подключению репозитория — на сайте NGINX. (&lt;a href="https://nginx.org/en/linux_packages.html" rel="noopener noreferrer"&gt;nginx.org&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Проверка:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
nginx &lt;span class="nt"&gt;-v&lt;/span&gt;

rpm &lt;span class="nt"&gt;-qa&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'^nginx'&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Если Nginx не из nginx.org — дальше тоже можно жить, но пакеты модулей и путь к .so могут отличаться.&lt;/p&gt;




&lt;p&gt;2) Установить динамический модуль GeoIP (legacy)&lt;/p&gt;

&lt;p&gt;Пакет модуля на RHEL/CentOS-семействе обычно называется &lt;strong&gt;nginx-module-geoip&lt;/strong&gt; (динамический модуль). Сам принцип — модуль ставится отдельно, затем подключается через load_module. (&lt;a href="https://nginx.org/en/docs/http/ngx_http_geoip_module.html" rel="noopener noreferrer"&gt;nginx.org&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Команды:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
dnf makecache

dnf &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; nginx-module-geoip

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Проверим, что файл модуля появился:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /usr/lib64/nginx/modules | &lt;span class="nb"&gt;grep &lt;/span&gt;geoip &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ожидаемо увидеть что-то вроде:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
ngx\_http\_geoip\_module.so

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;3) Подготовить каталог под базы GeoIP&lt;/p&gt;

&lt;p&gt;Создадим каталог, куда будем класть базы (как минимум country, опционально city):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /etc/nginx/geoip

&lt;span class="nb"&gt;chmod &lt;/span&gt;0755 /etc/nginx/geoip

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Важное примечание про базы (без розовых очков)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Официальный модуль ngx_http_geoip_module ожидает &lt;strong&gt;предкомпилированные базы MaxMind legacy формата&lt;/strong&gt;. (&lt;a href="https://nginx.org/en/docs/http/ngx_http_geoip_module.html" rel="noopener noreferrer"&gt;nginx.org&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Бесплатные GeoLite Legacy базы &lt;strong&gt;официально прекращены&lt;/strong&gt; (их больше не обновляют и не раздают). (&lt;a href="https://blog.maxmind.com/2022/06/geoip-legacy-databases-have-been-retired" rel="noopener noreferrer"&gt;MaxMind&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Что делать на практике:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Если у вас уже есть GeoIP.dat / GeoLiteCity.dat (из старых проектов, из внутреннего артефакт-хранилища, из коммерческой подписки) — кладём их в /etc/nginx/geoip/.&lt;/li&gt;
&lt;li&gt;Если нет — не тратьте время на “магические” левые ссылки: проще и правильнее перейти на GeoIP2 (см. путь B ниже) или использовать GeoIP только для грубой аналитики через внешний сервис.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Для варианта A предполагаем, что файлы у вас есть:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
пример: вы загрузили базы сами и положили в каталог

&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /etc/nginx/geoip

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ожидаемые имена (можно свои, главное путь правильно указать в конфиге):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;/etc/nginx/geoip/GeoIP.dat&lt;/li&gt;
&lt;li&gt;/etc/nginx/geoip/GeoLiteCity.dat&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;4) Подключить модуль и базы в конфиге Nginx (BitrixVM)&lt;/p&gt;

&lt;p&gt;Где править в BitrixVM&lt;/p&gt;

&lt;p&gt;В BitrixVM конфигурация обычно разнесена, но базовый путь стандартный:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;главный файл: /etc/nginx/nginx.conf&lt;/li&gt;
&lt;li&gt;дополнительные: /etc/nginx/conf.d/*.conf (и/или bitrix-специфичные include)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Нам важно соблюсти правило Nginx: load_module допускается &lt;strong&gt;только в основном контексте&lt;/strong&gt; (верх файла, до events {} / http {}). (&lt;a href="https://nginx.org/en/docs/http/ngx_http_geoip_module.html" rel="noopener noreferrer"&gt;nginx.org&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Сделаем аккуратно: создадим отдельный файл и подключим его в nginx.conf, если у вас уже есть include для модулей. Если include нет — вставьте строку load_module прямо в начало nginx.conf.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Вариант 1 (предпочтительно): отдельный файл&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Создаём /etc/nginx/conf.d/00-geoip-load.conf (если у вас conf.d инклюдится в main-контексте — это редкость). В большинстве схем conf.d подключается внутри http {}, поэтому этот вариант может не сработать.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Вариант 2 (надёжно): правим /etc/nginx/nginx.conf&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Открываем /etc/nginx/nginx.conf и добавляем в самое начало:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;
&lt;span class="k"&gt;load\_module&lt;/span&gt; &lt;span class="s"&gt;"/usr/lib64/nginx/modules/ngx&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_http&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_geoip&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_module.so"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Дальше — внутри http {} добавляем пути к базам:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;
&lt;span class="k"&gt;http&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="kn"&gt;geoip\_country&lt;/span&gt; &lt;span class="n"&gt;/etc/nginx/geoip/GeoIP.dat&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;geoip\_city&lt;/span&gt; &lt;span class="n"&gt;/etc/nginx/geoip/GeoLiteCity.dat&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;...&lt;/span&gt;

&lt;span class="err"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Это включает переменные $geoip_country_code, $geoip_country_name и пачку city-переменных. Список переменных и назначение описаны в документации модуля. (&lt;a href="https://nginx.org/en/docs/http/ngx_http_geoip_module.html" rel="noopener noreferrer"&gt;nginx.org&lt;/a&gt;)&lt;/p&gt;




&lt;p&gt;5) Добавить GeoIP в access.log и прокинуть заголовки к Apache/приложению&lt;/p&gt;

&lt;p&gt;5.1. Логирование (быстро увидеть пользу)&lt;/p&gt;

&lt;p&gt;В http {} определим формат лога, где будут страна/город:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;
&lt;span class="k"&gt;log\_format&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_geoip&lt;/span&gt; &lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="nv"&gt;$remote&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_addr&lt;/span&gt; &lt;span class="s"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;$remote&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_user&lt;/span&gt; &lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$time&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_local]&lt;/span&gt; &lt;span class="s"&gt;'&lt;/span&gt;

&lt;span class="s"&gt;'"&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt; &lt;span class="nv"&gt;$status&lt;/span&gt; &lt;span class="nv"&gt;$body&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_bytes&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_sent&lt;/span&gt; &lt;span class="s"&gt;'&lt;/span&gt;

&lt;span class="s"&gt;'"&lt;/span&gt;&lt;span class="nv"&gt;$http&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_referer"&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$http&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_user&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_agent"&lt;/span&gt; &lt;span class="s"&gt;'&lt;/span&gt;

&lt;span class="s"&gt;'country=&lt;/span&gt;&lt;span class="nv"&gt;$geoip&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_country&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_code&lt;/span&gt; &lt;span class="s"&gt;country&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_name="&lt;/span&gt;&lt;span class="nv"&gt;$geoip&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_country&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_name"&lt;/span&gt; &lt;span class="s"&gt;'&lt;/span&gt;

&lt;span class="s"&gt;'city="&lt;/span&gt;&lt;span class="nv"&gt;$geoip&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_city"&lt;/span&gt; &lt;span class="s"&gt;lat=&lt;/span&gt;&lt;span class="nv"&gt;$geoip&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_latitude&lt;/span&gt; &lt;span class="s"&gt;lon=&lt;/span&gt;&lt;span class="nv"&gt;$geoip&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_longitude'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;И используем его для access.log:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;
&lt;span class="k"&gt;access\_log&lt;/span&gt; &lt;span class="n"&gt;/var/log/nginx/access.log&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_geoip&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Если у вас уже задан свой log_format в BitrixVM — не ломайте чужое. Добавьте &lt;strong&gt;новый&lt;/strong&gt; main_geoip и подключите его точечно на нужном server {} через access_log ... main_geoip;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;5.2. Заголовки в апстрим (nginx -&amp;gt; httpd -&amp;gt; Bitrix)&lt;/p&gt;

&lt;p&gt;Если Nginx проксирует в Apache (типично для BitrixVM), вы можете прокинуть страну/город в бекенд заголовками. В вашем server {} или location ~ .php$ (где стоит proxy_pass или upstream на httpd) добавьте:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;
&lt;span class="k"&gt;proxy\_set\_header&lt;/span&gt; &lt;span class="s"&gt;X-GeoIP-Country&lt;/span&gt; &lt;span class="nv"&gt;$geoip&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_country&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_code&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;proxy\_set\_header&lt;/span&gt; &lt;span class="s"&gt;X-GeoIP-Country-Name&lt;/span&gt; &lt;span class="nv"&gt;$geoip&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_country&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;proxy\_set\_header&lt;/span&gt; &lt;span class="s"&gt;X-GeoIP-City&lt;/span&gt; &lt;span class="nv"&gt;$geoip&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_city&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Так приложение сможет читать это из $_SERVER['HTTP_X_GEOIP_COUNTRY'] и т.д.&lt;/p&gt;




&lt;p&gt;6) Если перед Nginx есть прокси/балансировщик: geoip_proxy&lt;/p&gt;

&lt;p&gt;Если Nginx стоит не “на краю”, а за прокси (Cloudflare, ELB, внешний балансировщик), GeoIP будет смотреть на IP прокси, а не клиента. Для этого модуль поддерживает geoip_proxy и geoip_proxy_recursive и учитывает X-Forwarded-For. (&lt;a href="https://nginx.org/en/docs/http/ngx_http_geoip_module.html" rel="noopener noreferrer"&gt;nginx.org&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Пример в http {}:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;
&lt;span class="k"&gt;geoip\_proxy&lt;/span&gt; &lt;span class="mf"&gt;10.0&lt;/span&gt;&lt;span class="s"&gt;.16.0/26&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;geoip\_proxy\_recursive&lt;/span&gt; &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Диапазон подставьте свой (подсеть балансировщика/прокси).&lt;/p&gt;




&lt;p&gt;Проверка результата&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Проверяем синтаксис конфига:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
nginx &lt;span class="nt"&gt;-t&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ожидаемо:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
syntax is ok

test is successful

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Перезагружаем:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
systemctl reload nginx

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Дёргаем сайт и смотрим лог:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
curl &lt;span class="nt"&gt;-I&lt;/span&gt; https://your-domain.tld/ | &lt;span class="nb"&gt;head

tail&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 5 /var/log/nginx/access.log

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;В конце строки лога должны появиться country=XX и другие поля. Если видите country=- — значит модуль жив, но база не отрабатывает (см. типичные ошибки ниже).&lt;/p&gt;




&lt;p&gt;Типичные ошибки&lt;/p&gt;

&lt;p&gt;❌ 1) unknown directive "geoip_country"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Причина:&lt;/strong&gt; модуль не загружен через load_module или установлен не тот пакет.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Как исправить:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;убедитесь, что модуль установлен: dnf install -y nginx-module-geoip&lt;/li&gt;
&lt;li&gt;проверьте наличие файла: ls /usr/lib64/nginx/modules/ngx_http_geoip_module.so&lt;/li&gt;
&lt;li&gt;проверьте, что load_module стоит &lt;strong&gt;в самом верху&lt;/strong&gt; /etc/nginx/nginx.conf (до http {}), иначе Nginx его просто не увидит. (&lt;a href="https://nginx.org/en/docs/http/ngx_http_geoip_module.html" rel="noopener noreferrer"&gt;nginx.org&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;❌ 2) Nginx стартует, но GeoIP-переменные пустые&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Причина:&lt;/strong&gt; база отсутствует/не читается/не legacy формата. Legacy GeoLite официально прекращены — это частая ловушка у инструкций “из 2016”. (&lt;a href="https://blog.maxmind.com/2022/06/geoip-legacy-databases-have-been-retired" rel="noopener noreferrer"&gt;MaxMind&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Как исправить:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;проверьте файлы: ls -la /etc/nginx/geoip&lt;/li&gt;
&lt;li&gt;проверьте права (Nginx должен читать): chmod 0644 /etc/nginx/geoip/*.dat&lt;/li&gt;
&lt;li&gt;если базы нет — планируйте переход на GeoLite2 + GeoIP2 модуль (см. ниже, путь B). (&lt;a href="https://docs.nginx.com/nginx-admin-guide/security-controls/controlling-access-by-geoip" rel="noopener noreferrer"&gt;docs.nginx.com&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;❌ 3) Хотите “закрыть страны” и тянет написать if&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Причина:&lt;/strong&gt; в Nginx if — не как в языках программирования. Есть известная рекомендация избегать сложных if-конструкций, и использовать map. При этом даже NGINX-экосистема признаёт, что безопасные случаи есть (обычно return/rewrite). (&lt;a href="https://nginx.org/en/docs/http/ngx_http_rewrite_module.html#if" rel="noopener noreferrer"&gt;nginx.org&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Как исправить (без фанатизма):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;для маршрутизации/флагов используйте map (переменная вычисляется один раз и дальше используется в логике)&lt;/li&gt;
&lt;li&gt;если всё-таки нужен if, ограничьте его до “проверка → return”, без set, без вложенных rewrite-цепочек (иначе потом сложно отлаживать). (&lt;a href="https://nginx.org/en/docs/http/ngx_http_rewrite_module.html#if" rel="noopener noreferrer"&gt;nginx.org&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Где применять&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prod на VPS (BitrixVM 9+, CentOS 9+)&lt;/strong&gt;: добавить страну/город в логи, передавать в приложение, включать доп. метрики в мониторинг.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker&lt;/strong&gt; : удобно, если Nginx на периметре контейнеров и вам нужно логировать географию запросов (но с базами там отдельная история).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI/CD&lt;/strong&gt; : можно проверять nginx -t и деплоить конфиги автоматически, чтобы GeoIP не ронял nginx на бою.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Связка nginx + httpd&lt;/strong&gt; : прокидывайте X-GeoIP-* заголовки в Apache и дальше в Bitrix, чтобы не городить логику в PHP.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Сниппеты по статье:&lt;/strong&gt; Nginx GeoIP: load_module и базы .dat · log_format и заголовки X-GeoIP · geoip_proxy за прокси/балансировщиком · Проверка модуля и баз (bash)&lt;/p&gt;

&lt;p&gt;Внутренние ссылки по теме (чтобы собрать цепочку на сайте):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://viku-lov.ru/snippets/nginx/" rel="noopener noreferrer"&gt;https://viku-lov.ru/snippets/nginx/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://viku-lov.ru/blog/" rel="noopener noreferrer"&gt;https://viku-lov.ru/blog/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://viku-lov.ru/services/devops/" rel="noopener noreferrer"&gt;https://viku-lov.ru/services/devops/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Путь B (коротко, но честно): GeoIP2 вместо legacy в 2025–2026&lt;/p&gt;

&lt;p&gt;Если ваша цель — &lt;strong&gt;актуальная&lt;/strong&gt; бесплатная геобаза, то правильная база — &lt;strong&gt;GeoLite2 (MMDB)&lt;/strong&gt;, а правильный модуль — &lt;strong&gt;GeoIP2&lt;/strong&gt;. В документации NGINX Plus GeoIP2 модуль описан как официальный динамический модуль. (&lt;a href="https://docs.nginx.com/nginx-admin-guide/dynamic-modules/geoip/" rel="noopener noreferrer"&gt;docs.nginx.com&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Legacy GeoIP/GeoLite в виде .dat в 2025–2026 — это в основном “наследие”, которое живёт только если у вас уже есть источник баз.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://viku-lov.ru/blog/kak-vklyuchit-geoip-v-nginx-peremennye-dlya-logov-i-zagolovkov" rel="noopener noreferrer"&gt;Read more on viku-lov.ru&lt;/a&gt;&lt;/p&gt;

</description>
      <category>nginx</category>
      <category>geoip</category>
      <category>bitrixvm</category>
      <category>production</category>
    </item>
    <item>
      <title>Как включить CPT в REST API WordPress: /wp/v2 отдаёт 404</title>
      <dc:creator>Андрей Викулов (VProger)</dc:creator>
      <pubDate>Mon, 16 Feb 2026 19:18:50 +0000</pubDate>
      <link>https://forem.com/_vproger_/kak-vkliuchit-cpt-v-rest-api-wordpress-wpv2-otdaiot-404-5gac</link>
      <guid>https://forem.com/_vproger_/kak-vkliuchit-cpt-v-rest-api-wordpress-wpv2-otdaiot-404-5gac</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcvpil7wup4yrfx12hx7q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcvpil7wup4yrfx12hx7q.png" alt="Как включить CPT в REST API WordPress: /wp/v2 отдаёт 404" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Как включить CPT в REST API WordPress: /wp/v2 отдаёт 404&lt;/p&gt;

&lt;p&gt;Если у вас &lt;strong&gt;wordpress custom post type rest api not working&lt;/strong&gt; , почти всегда это выглядит одинаково: CPT зарегистрирован, в админке есть, а запрос GET /wp-json/wp/v2/{post_type} возвращает &lt;strong&gt;404&lt;/strong&gt;. В конце получится нормальная регистрация CPT с REST-маршрутом в wp/v2, рабочим rest_base и поддержкой редактора блоков (Gutenberg). Опираемся только на параметры register_post_type() из официальной документации. (&lt;a href="https://developer.wordpress.org/reference/functions/register_post_type/" rel="noopener noreferrer"&gt;WordPress Developer Resources&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Сниппеты по статье:&lt;/strong&gt; CPT с REST в теме (functions.php) · CPT с REST в mu-plugin · Диагностика CPT и REST (get_post_type_object) · curl: проверка types и endpoint&lt;/p&gt;




&lt;p&gt;В чём проблема&lt;/p&gt;

&lt;p&gt;Симптом&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;CPT виден в админке (меню, список записей есть).&lt;/li&gt;
&lt;li&gt;В REST API endpoint отсутствует:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
curl &lt;span class="nt"&gt;-i&lt;/span&gt; https://example.com/wp-json/wp/v2/items

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ожидаемо при проблеме:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
HTTP/2 404

{"code":"rest\_no\_route","message":"No route was found matching the URL and request method.","data":{"status":404}}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Почему так происходит&lt;/p&gt;

&lt;p&gt;WordPress создаёт REST-маршруты для типа записи &lt;strong&gt;только если&lt;/strong&gt; при регистрации CPT указано:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;show_in_rest =&amp;gt; true&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Это прямо описано в официальной документации REST API (раздел про добавление поддержки REST для кастомных типов). (&lt;a href="https://developer.wordpress.org/rest-api/extending-the-rest-api/adding-rest-api-support-for-custom-content-types/" rel="noopener noreferrer"&gt;WordPress Developer Resources&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Параметры rest_base и rest_controller_class — тоже часть register_post_type() и управляют URL и контроллером маршрутов. (&lt;a href="https://developer.wordpress.org/reference/functions/register_post_type/" rel="noopener noreferrer"&gt;WordPress Developer Resources&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;По умолчанию show_in_rest в WordPress установлен в false: так не все типы записей автоматически попадают в API, что сохраняет обратную совместимость и снижает риск случайного раскрытия данных. Для CPT, который должен быть доступен через REST (и в редакторе блоков), параметр нужно явно включить.&lt;/p&gt;

&lt;p&gt;Полезные первоисточники:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;register_post_type() — полный список аргументов: &lt;a href="https://developer.wordpress.org/reference/functions/register_post_type/" rel="noopener noreferrer"&gt;https://developer.wordpress.org/reference/functions/register_post_type/&lt;/a&gt; (&lt;a href="https://developer.wordpress.org/reference/functions/register_post_type/" rel="noopener noreferrer"&gt;WordPress Developer Resources&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;REST API: поддержка кастомных типов: &lt;a href="https://developer.wordpress.org/rest-api/extending-the-rest-api/adding-rest-api-support-for-custom-content-types/" rel="noopener noreferrer"&gt;https://developer.wordpress.org/rest-api/extending-the-rest-api/adding-rest-api-support-for-custom-content-types/&lt;/a&gt; (&lt;a href="https://developer.wordpress.org/rest-api/extending-the-rest-api/adding-rest-api-support-for-custom-content-types/" rel="noopener noreferrer"&gt;WordPress Developer Resources&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Справочник с примерами (допустимый вторичный источник): &lt;a href="https://wp-kama.ru/function/register_post_type" rel="noopener noreferrer"&gt;wp-kama.ru: register_post_type&lt;/a&gt; (&lt;a href="https://wp-kama.ru/function/register_post_type" rel="noopener noreferrer"&gt;WordPress at Your Fingertips&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Рабочее решение&lt;/p&gt;

&lt;p&gt;Ниже — минимальный «боевой» вариант, который:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;добавит маршрут в wp/v2,&lt;/li&gt;
&lt;li&gt;задаст человеко-понятный endpoint (rest_base),&lt;/li&gt;
&lt;li&gt;использует стандартный контроллер WordPress,&lt;/li&gt;
&lt;li&gt;включит Gutenberg (через show_in_rest + supports =&amp;gt; editor).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Код ниже рассчитан на PHP 8.1+ и использует declare(strict_types=1) в соответствии с современной практикой (PSR, строгая типизация). Контроллер WP_REST_Posts_Controller::class — стандартный класс ядра WordPress, менять его не требуется. Оба варианта используют только API ядра и не требуют сторонних плагинов.&lt;/p&gt;

&lt;p&gt;Вариант 1: в теме (быстро, но не идеально для поддержки)&lt;/p&gt;

&lt;p&gt;Готовый вариант с пояснениями: сниппет «CPT с REST API в теме».&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Путь:&lt;/strong&gt; wp-content/themes/your-theme/functions.php&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict\_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;add\_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'init'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="nv"&gt;$labels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;

&lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;\_\_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Items'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'your-textdomain'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

&lt;span class="s1"&gt;'singular\_name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;\_\_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Item'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'your-textdomain'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="nf"&gt;register\_post\_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'item'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;

&lt;span class="s1"&gt;'labels'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$labels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="s1"&gt;'public'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="s1"&gt;'has\_archive'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="c1"&gt;// Gutenberg и REST&lt;/span&gt;

&lt;span class="s1"&gt;'show\_in\_rest'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="s1"&gt;'rest\_base'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'items'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="s1"&gt;'rest\_controller\_class'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WP\_REST\_Posts\_Controller&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="c1"&gt;// Чтобы редактор блоков был доступен&lt;/span&gt;

&lt;span class="s1"&gt;'supports'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'title'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'editor'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'excerpt'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'thumbnail'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;

&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="p"&gt;});&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Аргументы show_in_rest, rest_base, rest_controller_class официально поддерживаются register_post_type(). (&lt;a href="https://developer.wordpress.org/reference/functions/register_post_type/" rel="noopener noreferrer"&gt;WordPress Developer Resources&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Вариант 2: mu-plugin (лучше для prod)&lt;/p&gt;

&lt;p&gt;Тема может меняться, а CPT — обычно часть модели данных. Поэтому для продакшена чаще кладут в mu-plugin. Готовый код: сниппет «CPT с REST API в mu-plugin».&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Путь:&lt;/strong&gt; wp-content/mu-plugins/cpt-item.php&lt;/p&gt;

&lt;p&gt;(если папки mu-plugins нет — создайте)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;

&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nc"&gt;Plugin&lt;/span&gt; &lt;span class="nc"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;MU&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Item&lt;/span&gt; &lt;span class="nf"&gt;CPT&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;REST&lt;/span&gt; &lt;span class="n"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;*/&lt;/span&gt;

&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict\_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;add\_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'init'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="nf"&gt;register\_post\_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'item'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;

&lt;span class="s1"&gt;'label'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Items'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="s1"&gt;'public'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="s1"&gt;'show\_in\_rest'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="s1"&gt;'rest\_base'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'items'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="s1"&gt;'rest\_controller\_class'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WP\_REST\_Posts\_Controller&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="s1"&gt;'supports'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'title'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'editor'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;

&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Диагностика: убедиться, что WordPress реально зарегистрировал REST&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Посмотреть список типов в REST (сниппет «curl: проверка types и endpoint»):
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://example.com/wp-json/wp/v2/types | &lt;span class="nb"&gt;head&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Найти свой тип и проверить rest_base / show_in_rest. Удобно через небольшой debug-код (временно). Готовый mu-plugin: сниппет «Диагностика CPT и REST».&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Путь:&lt;/strong&gt; wp-content/mu-plugins/_debug-cpt.php&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict\_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;add\_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'init'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="nv"&gt;$obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get\_post\_type\_object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'item'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="nf"&gt;error\_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'CPT item: NOT registered'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;error\_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'CPT item: show\_in\_rest='&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$obj&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;show\_in\_rest&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;error\_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'CPT item: rest\_base='&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$obj&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rest\_base&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;error\_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'CPT item: rest\_controller\_class='&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$obj&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rest\_controller\_class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;999&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Если show_in_rest=0, маршрута в wp/v2 не будет — это норма по логике WordPress REST API. (&lt;a href="https://developer.wordpress.org/rest-api/extending-the-rest-api/adding-rest-api-support-for-custom-content-types/" rel="noopener noreferrer"&gt;WordPress Developer Resources&lt;/a&gt;)&lt;/p&gt;




&lt;p&gt;Проверка результата&lt;/p&gt;

&lt;p&gt;Проверка endpoint&lt;/p&gt;

&lt;p&gt;После деплоя кода выполните запрос к вашему домену (подставьте свой URL вместо example.com), например:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
curl &lt;span class="nt"&gt;-i&lt;/span&gt; https://example.com/wp-json/wp/v2/items

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ожидаем:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTTP 200&lt;/li&gt;
&lt;li&gt;JSON-массив записей (или пустой массив [], если пока нет элементов)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;В ответе должны быть поля в формате REST API WordPress: id, date, slug, title, content, excerpt и т.д. — в зависимости от того, что поддерживает ваш CPT. Если приходит 404, снова проверьте rest_base и наличие типа в /wp-json/wp/v2/types.&lt;/p&gt;

&lt;p&gt;Проверка, что маршрут существует&lt;/p&gt;

&lt;p&gt;Если вы не уверены, что именно создаётся, откройте в браузере:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://example.com/wp-json/" rel="noopener noreferrer"&gt;https://example.com/wp-json/&lt;/a&gt; — индекс API&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://example.com/wp-json/wp/v2/types" rel="noopener noreferrer"&gt;https://example.com/wp-json/wp/v2/types&lt;/a&gt; — типы, которые «видны» REST API (&lt;a href="https://developer.wordpress.org/rest-api/extending-the-rest-api/adding-rest-api-support-for-custom-content-types/" rel="noopener noreferrer"&gt;WordPress Developer Resources&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Проверка Gutenberg&lt;/p&gt;

&lt;p&gt;Если CPT поддерживает editor и включён show_in_rest, то редактор блоков для него доступен: это стандартное требование редактора, так как он получает и сохраняет данные через REST API. Связь параметра show_in_rest и работы Block Editor описана в официальной документации. (&lt;a href="https://developer.wordpress.org/block-editor/" rel="noopener noreferrer"&gt;Block Editor Handbook&lt;/a&gt;)&lt;/p&gt;




&lt;p&gt;Типичные ошибки&lt;/p&gt;

&lt;p&gt;1) ❌ show_in_rest =&amp;gt; true добавили, но всё равно 404&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Причина:&lt;/strong&gt; код регистрации не выполняется (не тот файл, условие, порядок хуков, фатал до init).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Как исправить:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;перенесите регистрацию в mu-plugins (самый надёжный вариант),&lt;/li&gt;
&lt;li&gt;убедитесь, что CPT регистрируется на init,&lt;/li&gt;
&lt;li&gt;временно залогируйте get_post_type_object() как в диагностике выше.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Официально REST-маршрут появляется только при show_in_rest=true. (&lt;a href="https://developer.wordpress.org/rest-api/extending-the-rest-api/adding-rest-api-support-for-custom-content-types/" rel="noopener noreferrer"&gt;WordPress Developer Resources&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;2) ❌ Endpoint другой, чем вы ожидаете&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Причина:&lt;/strong&gt; вы обращаетесь к /wp/v2/item, а rest_base не задан или задан иначе.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Как исправить:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;либо используйте дефолт (обычно равен имени пост-типа),&lt;/li&gt;
&lt;li&gt;либо явно задайте rest_base =&amp;gt; 'items' и обращайтесь к /wp-json/wp/v2/items. (&lt;a href="https://developer.wordpress.org/reference/functions/register_post_type/" rel="noopener noreferrer"&gt;WordPress Developer Resources&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;3) ❌ REST работает, но Gutenberg не появляется (или падает)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Причина:&lt;/strong&gt; CPT не поддерживает нужные фичи (supports) или отключён UI/редактор.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Как исправить:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;минимум: supports =&amp;gt; ['title', 'editor']&lt;/li&gt;
&lt;li&gt;проверьте, что show_ui не отключён, и CPT публичный/управляемый.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;supports — часть аргументов register_post_type() и напрямую влияет на доступность редактора. (&lt;a href="https://developer.wordpress.org/reference/functions/register_post_type/" rel="noopener noreferrer"&gt;WordPress Developer Resources&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;4) ❌ REST отдаёт 401/403 вместо 404&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Причина:&lt;/strong&gt; это уже не про маршрут, а про права/авторизацию или фильтры безопасности (rest_authentication_errors, плагины, WAF).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Как исправить (быстрый чек):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;проверьте запрос без авторизации на публичное чтение,&lt;/li&gt;
&lt;li&gt;временно отключите плагины безопасности,&lt;/li&gt;
&lt;li&gt;проверьте логи PHP/nginx и наличие кастомных фильтров на REST.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;5) ❌ После смены кода маршрут по-прежнему не появляется&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Причина:&lt;/strong&gt; кэш объекта типов записей или необходимость сброса постоянных ссылок. В некоторых конфигурациях WordPress кэширует список типов при первом обращении к REST.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Как исправить:&lt;/strong&gt; откройте в админке «Настройки → Постоянные ссылки» и нажмите «Сохранить» без изменений — это пересоздаёт правила. Убедитесь, что регистрация CPT выполняется на хуке init и ничто не отключает REST API для типа записи позже (фильтры rest_prepare_*, register_rest_route и т.п.).&lt;/p&gt;




&lt;p&gt;Где применять&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;prod / dev:&lt;/strong&gt; решение одинаково применимо в обеих средах. В продакшене обязательно выносите CPT в mu-plugins или отдельный плагин, чтобы смена темы не ломала REST API. В разработке удобно держать регистрацию в теме для быстрых правок.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;nginx / Apache:&lt;/strong&gt; поведение REST API от веб-сервера не зависит — важно лишь корректно проксировать запросы к PHP (например, try_files в nginx не должен перехватывать /wp-json/*).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;docker / CI/CD:&lt;/strong&gt; удобно проверять наличие маршрута curl-ом после деплоя (smoke-тест): curl -f &lt;a href="https://site/wp-json/wp/v2/items" rel="noopener noreferrer"&gt;https://site/wp-json/wp/v2/items&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BitrixVM тут ни при чём&lt;/strong&gt; , но подход тот же: конфиг должен быть повторяемым и не зависеть от «ручных правок в теме».&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Сниппеты&lt;/p&gt;

&lt;p&gt;Практичные фрагменты по теме статьи (официальные источники: developer.wordpress.org, wp-kama.ru):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CPT с REST API в теме (functions.php) — минимальная регистрация с show_in_rest, rest_base, WP_REST_Posts_Controller для вставки в тему.&lt;/li&gt;
&lt;li&gt;CPT с REST API в mu-plugin — тот же вариант для wp-content/mu-plugins; не зависит от темы, подходит для prod.&lt;/li&gt;
&lt;li&gt;Диагностика CPT и REST (get_post_type_object) — временный mu-plugin: логирование show_in_rest, rest_base, rest_controller_class в debug.log для поиска причины 404.&lt;/li&gt;
&lt;li&gt;curl: проверка types и endpoint — команды для проверки /wp-json/wp/v2/types и запроса к endpoint кастомного типа (HTTP-код и тело ответа).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://viku-lov.ru/blog/kak-vklyuchit-cpt-v-rest-api-wordpress-wp-v2-404" rel="noopener noreferrer"&gt;Read more on viku-lov.ru&lt;/a&gt;&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>customposttype</category>
      <category>restapi</category>
      <category>production</category>
    </item>
    <item>
      <title>Bitrix — проблема или просто инструмент?</title>
      <dc:creator>Андрей Викулов (VProger)</dc:creator>
      <pubDate>Mon, 16 Feb 2026 19:18:31 +0000</pubDate>
      <link>https://forem.com/_vproger_/bitrix-probliema-ili-prosto-instrumient-5aa8</link>
      <guid>https://forem.com/_vproger_/bitrix-probliema-ili-prosto-instrumient-5aa8</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F24f9nocml0cmmupny692.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F24f9nocml0cmmupny692.png" alt="Bitrix — проблема или просто инструмент?" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Bitrix — проблема или просто инструмент?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Коротко:&lt;/strong&gt; Bitrix — это не проблема; проблема возникает, когда его используют без архитектуры, понимания нагрузки и ограничений платформы. Ниже — полный разбор с контекстом, кейсом и границами применимости.&lt;/p&gt;

&lt;p&gt;С Bitrix я работаю пять лет, в веб-разработке в целом — семь. За это время внедрял, поддерживал, оптимизировал и реанимировал проекты разного масштаба: от корпоративных сайтов до B2B-платформ с интеграцией 1С, сложной бизнес-логикой и большим каталогом. Видел две крайности.&lt;/p&gt;

&lt;p&gt;Одни проекты работают стабильно, держат нагрузку, масштабируются и не создают постоянной боли.&lt;/p&gt;

&lt;p&gt;Другие — тормозят, падают, требуют «магии» при каждом релизе и вызывают раздражение у всей команды.&lt;/p&gt;

&lt;p&gt;И почти всегда причина не в CMS.&lt;/p&gt;




&lt;p&gt;В чём спор&lt;/p&gt;

&lt;p&gt;Bitrix часто обсуждают в эмоциональном ключе. Типичные утверждения:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;«Bitrix тормозит»&lt;/li&gt;
&lt;li&gt;«Это легаси»&lt;/li&gt;
&lt;li&gt;«На Laravel будет быстрее»&lt;/li&gt;
&lt;li&gt;«WordPress проще и легче»&lt;/li&gt;
&lt;li&gt;«Bitrix — сам по себе проблема»&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Звучит уверенно. Но почти никогда к таким заявлениям не прикладывают ни разбор запросов, ни цифры.&lt;/p&gt;

&lt;p&gt;Редко когда в обсуждении фигурируют:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;реальные SQL-запросы и план выполнения&lt;/li&gt;
&lt;li&gt;профилирование&lt;/li&gt;
&lt;li&gt;нагрузочное тестирование&lt;/li&gt;
&lt;li&gt;конфигурация сервера&lt;/li&gt;
&lt;li&gt;настройки кеширования&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Без этого разговор сводится к вкусовщине.&lt;/p&gt;




&lt;p&gt;Контекст: где Bitrix вообще уместен&lt;/p&gt;

&lt;p&gt;Bitrix — не универсальное решение.&lt;/p&gt;

&lt;p&gt;Он исторически заточен под:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;интеграции с 1С&lt;/li&gt;
&lt;li&gt;коммерческие проекты в РФ и СНГ&lt;/li&gt;
&lt;li&gt;сложные каталоги&lt;/li&gt;
&lt;li&gt;B2B-кабинеты&lt;/li&gt;
&lt;li&gt;многоуровневые роли доступа&lt;/li&gt;
&lt;li&gt;тяжёлую административную логику&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Если нужен лендинг или MVP за минимальный бюджет — Bitrix будет избыточен.&lt;/p&gt;

&lt;p&gt;Если нужна сложная экосистема с бухгалтерией, интеграцией, складом и управлением доступами — там он как раз раскрывается.&lt;/p&gt;

&lt;p&gt;Всё упирается в контекст задачи.&lt;/p&gt;




&lt;p&gt;Что на самом деле тормозит&lt;/p&gt;

&lt;p&gt;За годы работы почти не попадалось проектов, где «ядро Bitrix» было корнем всех бед.&lt;/p&gt;

&lt;p&gt;Зато регулярно встречалось вот что:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;отключённый композитный кеш&lt;/li&gt;
&lt;li&gt;выборки из инфоблоков без ограничений&lt;/li&gt;
&lt;li&gt;N+1 в ORM&lt;/li&gt;
&lt;li&gt;50 запросов там, где должно быть 5&lt;/li&gt;
&lt;li&gt;копипасту компонентов&lt;/li&gt;
&lt;li&gt;перегруженные инфоблоки&lt;/li&gt;
&lt;li&gt;неограниченные HL-блоки&lt;/li&gt;
&lt;li&gt;сервер с дефолтной конфигурацией&lt;/li&gt;
&lt;li&gt;отсутствие opcache&lt;/li&gt;
&lt;li&gt;отсутствие FastCGI cache&lt;/li&gt;
&lt;li&gt;php-fpm с одним воркером&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;После чего звучит: «Bitrix тормозит».&lt;/p&gt;

&lt;p&gt;Тормозит не Bitrix — тормозит то, как его реализовали и на чём запустили.&lt;/p&gt;




&lt;p&gt;Конкретный пример из практики&lt;/p&gt;

&lt;p&gt;Один из проектов: крупный каталог, B2B-кабинет, интеграция с 1С. Жалобы были стандартные: «Bitrix тормозит», «нужно переписывать». Разобрались за несколько дней без смены CMS.&lt;/p&gt;

&lt;p&gt;Симптомы были такие:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TTFB 1.8–2.3 секунды&lt;/li&gt;
&lt;li&gt;периодические 502&lt;/li&gt;
&lt;li&gt;рост CPU до 100%&lt;/li&gt;
&lt;li&gt;админка «задумывается» на 5–7 секунд&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Сделали по шагам:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;включили композитный кеш&lt;/li&gt;
&lt;li&gt;переписали выборки без N+1&lt;/li&gt;
&lt;li&gt;вынесли тяжёлые расчёты в фон&lt;/li&gt;
&lt;li&gt;включили opcache&lt;/li&gt;
&lt;li&gt;настроили php-fpm пул&lt;/li&gt;
&lt;li&gt;добавили FastCGI cache для анонимных&lt;/li&gt;
&lt;li&gt;оптимизировали nginx&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Итог:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TTFB стабильно ниже 300 мс&lt;/li&gt;
&lt;li&gt;нагрузка на CPU снизилась в разы&lt;/li&gt;
&lt;li&gt;502 исчезли&lt;/li&gt;
&lt;li&gt;админка стала отзывчивой&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CMS не меняли — сменили подход к архитектуре и настройке окружения.&lt;/p&gt;




&lt;p&gt;Когда Bitrix действительно работает&lt;/p&gt;

&lt;p&gt;Bitrix работает нормально, если есть внятная архитектура, понимание модели данных (инфоблоки, HL, связи), кеш используется осознанно, запросы контролируются (нет N+1 и выборок «всего подряд»), сервер настроен под нагрузку и есть кто-то с опытом — хотя бы один senior или архитектор, который может указать на узкие места.&lt;/p&gt;

&lt;p&gt;Bitrix — тяжёлая система. Тяжёлая не значит плохая: тяжёлый инструмент просто требует грамотного применения.&lt;/p&gt;




&lt;p&gt;Серверная сторона: о чём забывают чаще всего&lt;/p&gt;

&lt;p&gt;Многие оценивают CMS по ощущениям, не глядя на инфраструктуру: один и тот же Bitrix на дефолтном shared-хостинге и на настроенном VPS ведёт себя по-разному.&lt;/p&gt;

&lt;p&gt;Типичный прод выглядит так:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;VPS с минимальными ресурсами&lt;/li&gt;
&lt;li&gt;дефолтный nginx&lt;/li&gt;
&lt;li&gt;opcache выключен&lt;/li&gt;
&lt;li&gt;php-fpm без настройки&lt;/li&gt;
&lt;li&gt;нет мониторинга&lt;/li&gt;
&lt;li&gt;нет профилирования&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;И платформу объявляют виноватой.&lt;/p&gt;

&lt;p&gt;Грамотная конфигурация:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;opcache включён&lt;/li&gt;
&lt;li&gt;адекватные php-fpm процессы&lt;/li&gt;
&lt;li&gt;настроенный nginx&lt;/li&gt;
&lt;li&gt;FastCGI cache&lt;/li&gt;
&lt;li&gt;контроль SQL&lt;/li&gt;
&lt;li&gt;мониторинг нагрузки&lt;/li&gt;
&lt;li&gt;логирование&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bitrix в нормальной инфраструктуре ведёт себя иначе.&lt;/p&gt;




&lt;p&gt;Когда я бы не стал использовать Bitrix&lt;/p&gt;

&lt;p&gt;Если по делу: я не буду рекомендовать Bitrix:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;для простого лендинга&lt;/li&gt;
&lt;li&gt;для микро-проекта&lt;/li&gt;
&lt;li&gt;для MVP стартапа&lt;/li&gt;
&lt;li&gt;если нет команды с опытом&lt;/li&gt;
&lt;li&gt;если нет бюджета на инфраструктуру&lt;/li&gt;
&lt;li&gt;если нет архитектурного контроля&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;В таких случаях проще взять более лёгкий стек: тот же WordPress, Laravel или даже статику с формами. Проблема начинается, когда Bitrix выбирают «по привычке» или «как у всех», без анализа задачи, команды и бюджета на поддержку.&lt;/p&gt;




&lt;p&gt;Технический аспект без эмоций&lt;/p&gt;

&lt;p&gt;По пунктам:&lt;/p&gt;

&lt;p&gt;Производительность&lt;/p&gt;

&lt;p&gt;Зависит от кеширования, структуры данных, качества SQL, нагрузки и сервера. Не от названия CMS: «Bitrix» или «Laravel» сами по себе не быстрые и не медленные — бывает быстрая или медленная реализация и окружение.&lt;/p&gt;

&lt;p&gt;Масштабируемость&lt;/p&gt;

&lt;p&gt;Bitrix масштабируется, если данные структурированы, кеш продуман, а архитектура не превратилась в хаотичную доработку. Как и любая тяжёлая CMS, он требует дисциплины: без неё любой стек рано или поздно упирается в потолок.&lt;/p&gt;

&lt;p&gt;Поддержка&lt;/p&gt;

&lt;p&gt;Да, Bitrix сложнее, чем WordPress: больше сущностей, своя документация, обновления ядра и модулей. Но в крупных проектах он даёт строгую бизнес-логику, внятный контроль ролей и возможность держать архитектуру в рамках, а не в виде россыпи скриптов.&lt;/p&gt;

&lt;p&gt;DevOps-сложность&lt;/p&gt;

&lt;p&gt;Выше, чем у простого LAMP. Это плата за гибкость, интеграции и объём встроенного функционала: окружение нужно настраивать осознанно, а не «из коробки».&lt;/p&gt;




&lt;p&gt;Почему возникает репутация «проблемной CMS»&lt;/p&gt;

&lt;p&gt;Основные причины:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Проекты делают без внятной архитектуры.&lt;/li&gt;
&lt;li&gt;Крутят на минимальном хостинге и удивляются лагам.&lt;/li&gt;
&lt;li&gt;Копируют чужие решения, не разбираясь в последствиях.&lt;/li&gt;
&lt;li&gt;Не проводят профилирование и не смотрят тяжёлые запросы.&lt;/li&gt;
&lt;li&gt;Не считают нагрузку до запуска в прод.&lt;/li&gt;
&lt;li&gt;Экономят на опытном разработчике или архитекторе.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;В большинстве плохих кейсов виноват не «плохой Bitrix», а отсутствие нормального инженерного подхода.&lt;/p&gt;




&lt;p&gt;Типичные ошибки при принятии решения&lt;/p&gt;

&lt;p&gt;Сводятся к одному: выбор и эксплуатация без анализа.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Выбирают по привычке&lt;/strong&gt; — «у нас всегда был Bitrix» или «заказчик просит» — без оценки задачи и команды.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Копируют чужую архитектуру&lt;/strong&gt; — взяли конфиг с другого проекта, не подогнали под нагрузку и модель данных.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Не закладывают время на настройку окружения&lt;/strong&gt; — считают, что «поставили и поехало», без opcache, кеша и мониторинга.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Не думают о поддержке через год-два&lt;/strong&gt; — кто будет разбираться в кастомных доработках и обновлениях.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Исправлять такое уже в проде дороже, чем сразу заложить нормальный старт и окружение. Поэтому перед выбором стека имеет смысл честно оценить задачу, команду и бюджет на инфраструктуру. Границу применимости инструмента лучше понимать до старта проекта, а не после первых жалоб на производительность и срочных фиксов в проде.&lt;/p&gt;




&lt;p&gt;Главное заблуждение&lt;/p&gt;

&lt;p&gt;«Если сменить CMS, всё станет быстрее».&lt;/p&gt;

&lt;p&gt;Иногда смена стека действительно помогает — когда задача не подходит под текущий инструмент. Но чаще проблемы мигрируют вместе с проектом: архитектура остаётся рыхлой, нагрузку по-прежнему не считают, сервер не настраивают. В итоге новая система через полгода начинает «тормозить» так же, и винят уже её. Корень не в названии CMS, а в процессе принятия решений и в уровне эксплуатации.&lt;/p&gt;




&lt;p&gt;Моя позиция&lt;/p&gt;

&lt;p&gt;Bitrix — инструмент. Плохим или хорошим его не назовёшь: он просто требовательный к рукам и к окружению.&lt;/p&gt;

&lt;p&gt;Без понимания архитектуры, кеширования и нагрузки он будет выглядеть проблемой. При грамотном использовании решает задачи, которые на других CMS тянут за собой кучу кастомной разработки.&lt;/p&gt;

&lt;p&gt;Проблема не в платформе, а в том, как команда подходит к инженерии. Речь уже не про CMS, а про уровень команды. Если такой разбор без маркетинговой шелухи полезен — в блоге есть и пошаговые инструкции по Bitrix, и другие экспертные тексты.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://viku-lov.ru/blog/bitrix-problema-ili-prosto-instrument" rel="noopener noreferrer"&gt;Read more on viku-lov.ru&lt;/a&gt;&lt;/p&gt;

</description>
      <category>bitrix</category>
      <category>1cbitrix</category>
      <category>backend</category>
      <category>devops</category>
    </item>
    <item>
      <title>WordPress: ACF поле не сохраняется — чиним запись meta</title>
      <dc:creator>Андрей Викулов (VProger)</dc:creator>
      <pubDate>Sun, 15 Feb 2026 07:45:22 +0000</pubDate>
      <link>https://forem.com/_vproger_/wordpress-acf-polie-nie-sokhraniaietsia-chinim-zapis-meta-2d9l</link>
      <guid>https://forem.com/_vproger_/wordpress-acf-polie-nie-sokhraniaietsia-chinim-zapis-meta-2d9l</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fckamtkowuujt2j3r8tcf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fckamtkowuujt2j3r8tcf.png" alt="WordPress: ACF поле не сохраняется — чиним запись meta" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;WordPress: ACF поле не сохраняется — чиним запись meta&lt;/p&gt;

&lt;p&gt;Запрос &lt;strong&gt;acf поле не сохраняется wordpress&lt;/strong&gt; обычно прилетает так: ACF-поля в админке есть, вводишь значение, жмёшь «Обновить», а потом пусто — и get_field() возвращает null или пустую строку. В конце этой инструкции: meta реально пишется в wp_postmeta, get_field() стабильно отдаёт значение, а конфликты с хуками перестают устраивать тихий саботаж.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Сниппеты по статье:&lt;/strong&gt; ACF Save Debug (MU-plugin) · ACF save_post с проверкой autosave/revision · WP-CLI: проверка post meta для ACF · ACF update_value: нормализация значения&lt;/p&gt;




&lt;p&gt;В чём проблема&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Симптомы:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ACF-поля отображаются в админке.&lt;/li&gt;
&lt;li&gt;После сохранения значение пропадает.&lt;/li&gt;
&lt;li&gt;В базе в wp_postmeta ключа либо нет, либо он не меняется.&lt;/li&gt;
&lt;li&gt;get_field('my_field', $post_id) возвращает пусто.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Самая частая причина на проде:&lt;/strong&gt; конфликт хуков сохранения.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ACF сохраняет значения при acf/save_post.&lt;/li&gt;
&lt;li&gt;Ваши (или чужие) функции на save_post/wp_insert_post/pre_post_update могут:&lt;/li&gt;
&lt;li&gt;срабатывать на &lt;strong&gt;autosave&lt;/strong&gt; или &lt;strong&gt;revision&lt;/strong&gt; и перетирать meta;&lt;/li&gt;
&lt;li&gt;делать wp_update_post() внутри save_post и запускать каскад «сохранился → пересохранился → перезатёрлось»;&lt;/li&gt;
&lt;li&gt;вызывать update_field()/update_post_meta() в неправильный момент (до/вместо ACF-сейва);&lt;/li&gt;
&lt;li&gt;чистить $_POST (да, такое тоже бывает — “безопасность” уровня «удалить данные»).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Почему autosave/revision важны:&lt;/strong&gt; WordPress создаёт автосохранения и ревизии, и если ваш хук не фильтрует их, он сработает «не на тот пост» и/или перезапишет meta “пустым”. Для проверки есть wp_is_post_autosave() и wp_is_post_revision().&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Типичный сценарий на проде:&lt;/strong&gt; в логе нет ошибок PHP, в админке всё сохраняется “без жалоб”, но на фронте или в REST API поле пустое. Часто виноват не сам ACF, а код темы или плагина на save_post с приоритетом 10: он срабатывает в своём порядке и перезаписывает meta до или после ACF. Ещё вариант — объектный кеш (Redis/Memcached): старые значения meta кешируются, и даже при корректной записи в БД ты видишь пустое значение, пока кеш не сбросится.&lt;/p&gt;




&lt;p&gt;Рабочее решение&lt;/p&gt;

&lt;p&gt;Ниже — рабочий, «боевой» путь: сначала &lt;strong&gt;диагностируем&lt;/strong&gt; , потом &lt;strong&gt;переносим вашу логику на правильный хук ACF&lt;/strong&gt; и выключаем перезапись на autosave/revision.&lt;/p&gt;

&lt;p&gt;1) Включаем логирование на prod без плясок&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Файл:&lt;/strong&gt; wp-config.php&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;
&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'WP\_DEBUG'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'WP\_DEBUG\_LOG'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'WP\_DEBUG\_DISPLAY'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Дальше хватаем лог:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; wp-content/debug.log

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Если у тебя Nginx + PHP-FPM — параллельно полезно смотреть error.log PHP-FPM / nginx, но debug.log обычно хватает. После правок не забудь перезагрузить PHP (или сервис PHP-FPM), иначе старый описанный конфиг не подхватится.&lt;/p&gt;




&lt;p&gt;2) Быстрая проверка: ACF вообще отправляет данные?&lt;/p&gt;

&lt;p&gt;Сделаем mu-plugin (так надёжнее: не отключится вместе с темой). Готовый вариант с пояснениями: сниппет «ACF Save Debug (MU-plugin)».&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Файл:&lt;/strong&gt; wp-content/mu-plugins/acf-save-debug.php&lt;/p&gt;

&lt;p&gt;(если папки нет — создай)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;

&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nc"&gt;Plugin&lt;/span&gt; &lt;span class="nc"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;ACF&lt;/span&gt; &lt;span class="nc"&gt;Save&lt;/span&gt; &lt;span class="nf"&gt;Debug&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;MU&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;*/&lt;/span&gt;

&lt;span class="nf"&gt;add\_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'acf/save\_post'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="n"&gt;\_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="c1"&gt;// Логируем только админку и только реальные посты&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;is\_admin&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ACF сохраняет значения из $\_POST['acf'] (с версии 5+).&lt;/span&gt;

&lt;span class="nv"&gt;$keys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;isset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;\_POST&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'acf'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;is\_array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;\_POST&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'acf'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;array\_keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;\_POST&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'acf'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="nf"&gt;error\_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'[ACF] acf/save\_post post\_id='&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="n"&gt;\_id&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;' keys='&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nb"&gt;implode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;','&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$keys&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Что это даёт:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Если в логе &lt;strong&gt;keys пустой&lt;/strong&gt; — ACF не получает значения (не тот экран, не тот post_id, поля disabled/readonly, конфликт JS/редактора, кастомная форма без acf_form_head() и т.д.).&lt;/li&gt;
&lt;li&gt;Если keys &lt;strong&gt;есть&lt;/strong&gt; , но meta не меняется — конфликт записи/перезапись ниже по цепочке.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Сохрани пост с заполненным ACF-полем и сразу открой debug.log: должна появиться строка с нужным post_id и списком ключей из $_POST['acf']. Если ключей нет — проблема на этапе отправки формы (фронт или другой плагин режет данные).&lt;/p&gt;




&lt;p&gt;3) Проверяем, пишется ли meta в базу (WP-CLI)&lt;/p&gt;

&lt;p&gt;Команды и порядок проверки: сниппет «WP-CLI: проверка post meta для ACF». Сначала узнаём post_id и имя поля (name). Дальше:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
wp post meta list 123 &lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;table

wp post meta get 123 my&lt;span class="se"&gt;\_&lt;/span&gt;field

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;WP-CLI команды по meta — официальные, стабильные.&lt;/p&gt;

&lt;p&gt;Если поле ACF не в my_field, а хранится иначе (например, field_XXXXXXXX как reference) — ты это тоже увидишь в списке. Так можно быстро убедиться, что запись в БД вообще происходит: если после сохранения в админке в wp post meta list появляется нужный ключ с актуальным значением, значит ACF и база в порядке, а проблема может быть в кеше или в месте, откуда ты читаешь поле.&lt;/p&gt;




&lt;p&gt;4) Убираем конфликт: переносим вашу логику с save_post на acf/save_post&lt;/p&gt;

&lt;p&gt;Если у тебя (или у темы/плагина) есть код вроде этого:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;
&lt;span class="nf"&gt;add\_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'save\_post'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="n"&gt;\_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="nf"&gt;update\_post\_meta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="n"&gt;\_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'my\_field'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;\_POST&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'something'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;});&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;…то он легко убьёт ACF, потому что:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;сработает на autosave/revision;&lt;/li&gt;
&lt;li&gt;может записать пустоту раньше/позже ACF;&lt;/li&gt;
&lt;li&gt;может триггерить пересохранение.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Правильная точка интеграции для ACF-полей — acf/save_post.&lt;/strong&gt; Готовый MU-plugin с проверкой autosave/revision и примером update_field: сниппет «ACF save_post с проверкой autosave/revision».&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Файл:&lt;/strong&gt; wp-content/mu-plugins/acf-save-fix.php&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;

&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nc"&gt;Plugin&lt;/span&gt; &lt;span class="nc"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;ACF&lt;/span&gt; &lt;span class="nc"&gt;Save&lt;/span&gt; &lt;span class="nf"&gt;Fix&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;MU&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;*/&lt;/span&gt;

&lt;span class="c1"&gt;// 20 = "после того, как ACF обработал свои данные" (часто оптимально на практике)&lt;/span&gt;

&lt;span class="nf"&gt;add\_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'acf/save\_post'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="n"&gt;\_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="c1"&gt;// 1) Не трогаем автосохранения и ревизии.&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;wp\_is\_post\_autosave&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="n"&gt;\_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nf"&gt;wp\_is\_post\_revision&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="n"&gt;\_id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// 2) На всякий — работаем только в админке&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;is\_admin&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// 3) Пример: если нужно вычислить и сохранить зависимое значение в ACF поле&lt;/span&gt;

&lt;span class="c1"&gt;// (update\_field — официальный путь для записи ACF значения).&lt;/span&gt;

&lt;span class="nv"&gt;$value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get\_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'source\_field'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="n"&gt;\_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nv"&gt;$computed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mb\_strtoupper&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;update\_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'my\_field'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$computed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="n"&gt;\_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ключевая идея: &lt;strong&gt;не мешай ACF сохраняться&lt;/strong&gt; , а дополняй его &lt;em&gt;после&lt;/em&gt; сейва, на acf/save_post. Приоритет 20 выбран не случайно: ACF сам вешает обработчик на acf/save_post, и к моменту срабатывания твоего кода с приоритетом 20 значения полей уже записаны в wp_postmeta. Если нужна логика “сначала ACF, потом я” — используй приоритет 20 или выше; если “сначала я, потом ACF” — приоритет меньше 10 (осторожно: не перезаписывай meta пустым).&lt;/p&gt;




&lt;p&gt;5) Если у тебя фильтр acf/update_value — не делай там записи в БД&lt;/p&gt;

&lt;p&gt;acf/update_value — фильтр для &lt;strong&gt;модификации значения перед сохранением&lt;/strong&gt; , а не для побочных эффектов. Пример нормализации (trim, регистр) без записи в другие поля: сниппет «ACF update_value: нормализация значения».&lt;/p&gt;

&lt;p&gt;То есть норм:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;
&lt;span class="nf"&gt;add\_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'acf/update\_value/name=my\_field'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="n"&gt;\_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$field&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="c1"&gt;// Нормализуем то, что сохраняем&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;is\_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nb"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;А вот &lt;strong&gt;делать update_field() внутри acf/update_value&lt;/strong&gt; — частый путь к рекурсии/гонкам/«то пусто, то густо». Для побочных сохранений используй acf/save_post, как выше.&lt;/p&gt;

&lt;p&gt;6) Приоритет хука и порядок выполнения&lt;/p&gt;

&lt;p&gt;Иногда поле сохраняется “через раз” или только при втором нажатии «Обновить». Это часто связано с тем, что несколько обработчиков висят на acf/save_post или save_post с разными приоритетами и перезаписывают друг друга. Проверь все места, где вызываются update_post_meta() или update_field() для этого поста: убедись, что они либо на acf/save_post с приоритетом не меньше 15, либо явно пропускают autosave/revision. Один “грязный” хук на приоритете 5 может обнулить meta до того, как ACF успеет записать данные.&lt;/p&gt;




&lt;p&gt;Проверка результата&lt;/p&gt;

&lt;p&gt;1) Проверка в админке&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Открой пост/страницу с ACF.&lt;/li&gt;
&lt;li&gt;Введи тестовое значение.&lt;/li&gt;
&lt;li&gt;Сохрани.&lt;/li&gt;
&lt;li&gt;Перезагрузи страницу редактирования — значение должно остаться.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Если значение пропало после перезагрузки, повтори цепочку из раздела «Рабочее решение»: проверь лог на наличие ключей в acf/save_post, затем через WP-CLI убедись, что meta есть в базе. Если в БД значение есть, а в админке пусто — возможен кеш страницы или кеш объекта (Redis/Memcached).&lt;/p&gt;

&lt;p&gt;2) Проверка через WP-CLI&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
wp post meta get 123 my&lt;span class="se"&gt;\_&lt;/span&gt;field

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ожидаешь увидеть сохранённое значение.&lt;/p&gt;

&lt;p&gt;3) Проверка чтения через PHP (на шаблоне/в плагине)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;
&lt;span class="nv"&gt;$value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get\_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'my\_field'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;var\_dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;get_field() официально возвращает значение поля из нужной “локации” (post/user/term/options) — важно, чтобы post_id был правильный. На проде после успешной записи имеет смысл один раз сбросить объектный кеш (если используется): wp cache flush через WP-CLI или сброс в панели хостинга, чтобы не ловить старые значения из кеша.&lt;/p&gt;




&lt;p&gt;Типичные ошибки&lt;/p&gt;

&lt;p&gt;❌ 1) Хук save_post перезаписывает meta пустым&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Причина:&lt;/strong&gt; хук срабатывает на autosave/revision или порядок хуков перетирает ACF.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Исправление:&lt;/strong&gt; перенеси логику на acf/save_post, добавь проверки wp_is_post_autosave()/wp_is_post_revision().&lt;/p&gt;

&lt;p&gt;❌ 2) Неверный post_id для get_field()&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Причина:&lt;/strong&gt; поле не у поста, а у options/user/term, а ты читаешь как пост.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Исправление:&lt;/strong&gt; передай правильный post_id (например, option, user_{$id}, term_{$id} — по модели ACF) и проверь по документации get_field().&lt;/p&gt;

&lt;p&gt;❌ 3) “Я модифицирую значение в acf/update_value, но оно не сохраняется”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Причина:&lt;/strong&gt; ты возвращаешь null/пустоту, либо делаешь побочные записи в БД прямо в фильтре.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Исправление:&lt;/strong&gt; в acf/update_value только возвращай нормализованное $value. Любые доп. сохранения — в acf/save_post.&lt;/p&gt;

&lt;p&gt;❌ 4) Поле “видно”, но $_POST['acf'] пустой&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Причина:&lt;/strong&gt; кастомная форма/метабокс, конфликт JS, поле disabled/readonly, или это вообще не ACF экран.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Исправление:&lt;/strong&gt; смотри лог mu-plugin, проверь что ACF реально сабмитит $_POST&lt;a href="https://dev.to%D0%B2%20ACF%205+%20%D1%8D%D1%82%D0%BE%20%D0%B8%D0%BC%D0%B5%D0%BD%D0%BD%D0%BE%20acf"&gt;'acf'&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;❌ 5) В БД meta есть, а на фронте или в API пусто&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Причина:&lt;/strong&gt; объектный кеш (Redis, Memcached, кеш плагина) отдаёт старую версию meta; или читаешь поле в неправильном контексте (например, в цикле без передачи post_id).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Исправление:&lt;/strong&gt; выполни wp cache flush или сброс кеша в админке; при вызове get_field() всегда передавай второй аргумент — ID поста, термина или 'option'.&lt;/p&gt;

&lt;p&gt;❌ 6) Поле внутри группы (repeater/flexible) не сохраняется&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Причина:&lt;/strong&gt; обращаешься к полю по имени без учёта группы или подполя; при ручном update_post_meta перезаписываешь сериализованные данные группы.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Исправление:&lt;/strong&gt; используй get_field('group_name', $post_id) и вложенные ключи для подполей; для записи — только update_field() с путём к полю (например, my_repeater_0_my_subfield или через массив по документации ACF).&lt;/p&gt;




&lt;p&gt;Если не работает: чеклист диагностики за 10 минут&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Убедись, что acf/save_post вообще срабатывает&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Смотри wp-content/debug.log и строку вида [ACF] acf/save_post post_id=... keys=....&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Посмотри, кто трогает meta кроме ACF&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Поиск по проекту:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; &lt;span class="s2"&gt;"save&lt;/span&gt;&lt;span class="se"&gt;\_&lt;/span&gt;&lt;span class="s2"&gt;post"&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; wp-content/themes wp-content/plugins wp-content/mu-plugins

&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; &lt;span class="s2"&gt;"update&lt;/span&gt;&lt;span class="se"&gt;\_&lt;/span&gt;&lt;span class="s2"&gt;post&lt;/span&gt;&lt;span class="se"&gt;\_&lt;/span&gt;&lt;span class="s2"&gt;meta"&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; wp-content/themes wp-content/plugins wp-content/mu-plugins

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Временно отключи подозреваемый плагин/кусок темы&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;На проде аккуратно: сначала на staging. Но если выбора нет — выключай точечно, не “всё подряд”.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Проверь meta напрямую в БД&lt;/strong&gt; (если есть доступ)
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_value&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;wp&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_postmeta&lt;/span&gt;

&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt;

&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Проверь, что читаешь то же поле, что сохраняешь&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;В ACF у поля есть name и key. Для update_field() можно передавать и то, и другое — документация это допускает и это часто спасает при переименованиях.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Сбрось кеш&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Если на сервере включён объектный кеш или кеш страниц, после исправления кода выполни сброс: wp cache flush, очистка кеша в панели хостинга или перезапуск Redis/Memcached. Иначе можно долго отлаживать “правильный” код, глядя на закешированный результат.&lt;/p&gt;




&lt;p&gt;Где применять&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;prod&lt;/strong&gt; — да, именно здесь чаще всего и всплывает (конфликт плагинов/хуков, кеши, кастомные сохранения). Рецепт тот же: логи, WP-CLI, перенос логики на acf/save_post и проверка приоритетов.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;docker/staging&lt;/strong&gt; — идеально, чтобы отловить “кто перетирает meta” до выката. Можно временно включить debug.log и поставить mu-plugin для логирования без риска для продакшена.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI/CD&lt;/strong&gt; — можно добавить быстрый smoke-тест через WP-CLI: записать meta и прочитать обратно; при падении теста сразу видно, что сохранение или чтение полей сломалось.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Сниппеты по статье:&lt;/strong&gt; ACF Save Debug (MU-plugin) · ACF save_post с autosave/revision · WP-CLI post meta для ACF · ACF update_value: нормализация&lt;/p&gt;

&lt;p&gt;Пара внутренних ссылок, чтобы не искать по сайту:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://viku-lov.ru/blog" rel="noopener noreferrer"&gt;https://viku-lov.ru/blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://viku-lov.ru/snippets" rel="noopener noreferrer"&gt;https://viku-lov.ru/snippets&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://viku-lov.ru/services" rel="noopener noreferrer"&gt;https://viku-lov.ru/services&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://viku-lov.ru/projects" rel="noopener noreferrer"&gt;https://viku-lov.ru/projects&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://viku-lov.ru/blog/acf-pole-ne-sohranyaetsya-wordpress" rel="noopener noreferrer"&gt;Read more on viku-lov.ru&lt;/a&gt;&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>acf</category>
      <category>meta</category>
      <category>production</category>
    </item>
    <item>
      <title>WordPress: ускоряем Nginx + PHP-FPM до TTFB меньше 300 мс</title>
      <dc:creator>Андрей Викулов (VProger)</dc:creator>
      <pubDate>Fri, 13 Feb 2026 22:52:00 +0000</pubDate>
      <link>https://forem.com/_vproger_/wordpress-uskoriaiem-nginx-php-fpm-do-ttfb-mienshie-300-ms-2g4c</link>
      <guid>https://forem.com/_vproger_/wordpress-uskoriaiem-nginx-php-fpm-do-ttfb-mienshie-300-ms-2g4c</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhbnqk8fxggq9u2nemtul.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhbnqk8fxggq9u2nemtul.png" alt="WordPress: ускоряем Nginx + PHP-FPM до TTFB меньше 300 мс" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;WordPress: ускоряем Nginx + PHP-FPM до TTFB меньше 300 мс&lt;/p&gt;

&lt;p&gt;Если &lt;strong&gt;wordpress долго загружается nginx php-fpm&lt;/strong&gt; , TTFB 2–5 секунд, CPU стабильно загружен, а плагинов немного — проблема почти всегда в конфигурации PHP-FPM, OPcache и отсутствии fastcgi_cache.&lt;/p&gt;

&lt;p&gt;После настройки:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TTFB меньше 300 мс&lt;/li&gt;
&lt;li&gt;CPU без перегруза&lt;/li&gt;
&lt;li&gt;OPcache реально работает&lt;/li&gt;
&lt;li&gt;Nginx кеширует анонимный трафик&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Без Docker. Обычный VPS в prod.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Сниппеты по статье:&lt;/strong&gt; OPcache для WordPress (php.ini) · PHP-FPM pool (pm dynamic) · Nginx fastcgi_cache для WordPress · Проверка TTFB (curl)&lt;/p&gt;




&lt;p&gt;В чём проблема&lt;/p&gt;

&lt;p&gt;Симптомы:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Страница открывается 2–5 секунд&lt;/li&gt;
&lt;li&gt;curl -w "%{time_starttransfer}" показывает TTFB 1.8–3.5 сек&lt;/li&gt;
&lt;li&gt;CPU 70–100%&lt;/li&gt;
&lt;li&gt;top показывает php-fpm в топе процессов&lt;/li&gt;
&lt;li&gt;Кеш-плагинов нет или они не решают проблему&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Проверка TTFB (сниппет: curl TTFB):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
curl &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"TTFB: %{time&lt;/span&gt;&lt;span class="se"&gt;\_&lt;/span&gt;&lt;span class="s2"&gt;starttransfer}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; https://example.com

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Если видите:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
TTFB: 2.341221

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;— PHP генерирует страницу каждый раз.&lt;/p&gt;

&lt;p&gt;Nginx не кеширует.&lt;/p&gt;

&lt;p&gt;OPcache либо отключён, либо настроен плохо.&lt;/p&gt;

&lt;p&gt;Причины обычно такие:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;OPcache выключен или память 64MB.&lt;/li&gt;
&lt;li&gt;PHP-FPM работает с 5 процессами при 8 ядрах.&lt;/li&gt;
&lt;li&gt;Нет fastcgi_cache.&lt;/li&gt;
&lt;li&gt;Неправильно настроен bypass для админки.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Как отличить от других причин: если отключить все плагины (переименовать папку wp-content/plugins) и TTFB всё равно высокий — дело в стеке (Nginx/PHP-FPM/OPcache), а не в коде темы или плагинах. Если после отключения плагинов стало быстро — оптимизируйте тяжёлые плагины или кеш на уровне приложения. Здесь разбираем именно случай «плагинов немного, но стек не настроен».&lt;/p&gt;

&lt;p&gt;WordPress сам по себе не медленный. Медленный — стек. Если после настройки появятся 502/504 — смотрите Nginx и PHP-FPM: ошибки 502, 504. Конфиг виртуальных хостов — в настройке server blocks для нескольких проектов.&lt;/p&gt;




&lt;p&gt;Рабочее решение&lt;/p&gt;

&lt;p&gt;1️⃣ Настраиваем OPcache (PHP 8.2)&lt;/p&gt;

&lt;p&gt;Готовый блок с пояснениями: сниппет «OPcache для WordPress (php.ini)». Ниже — кратко.&lt;/p&gt;

&lt;p&gt;Файл:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
/etc/php/8.2/fpm/php.ini

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ищем блок OPcache и приводим к нормальному виду:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;
&lt;span class="py"&gt;opcache.enable&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;

&lt;span class="err"&gt;opcache.enable\&lt;/span&gt;&lt;span class="py"&gt;_cli&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;

&lt;span class="err"&gt;opcache.memory\&lt;/span&gt;&lt;span class="py"&gt;_consumption&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;256&lt;/span&gt;

&lt;span class="err"&gt;opcache.interned\_strings\&lt;/span&gt;&lt;span class="py"&gt;_buffer&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;32&lt;/span&gt;

&lt;span class="err"&gt;opcache.max\_accelerated\&lt;/span&gt;&lt;span class="py"&gt;_files&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;20000&lt;/span&gt;

&lt;span class="err"&gt;opcache.validate\&lt;/span&gt;&lt;span class="py"&gt;_timestamps&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;

&lt;span class="err"&gt;opcache.revalidate\&lt;/span&gt;&lt;span class="py"&gt;_freq&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;

&lt;span class="err"&gt;opcache.fast\&lt;/span&gt;&lt;span class="py"&gt;_shutdown&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Почему так:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;256MB — минимум для продакшена&lt;/li&gt;
&lt;li&gt;validate_timestamps=0 — без постоянной проверки файлов&lt;/li&gt;
&lt;li&gt;20 000 файлов — WordPress + плагины легко съедают 10k+&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Путь к php.ini может отличаться: на Debian/Ubuntu это обычно /etc/php/8.2/fpm/php.ini, на CentOS/Rocky — /etc/php.ini или отдельный файл в /etc/php.d/. Убедитесь, что правите именно конфиг FPM, а не CLI: php -i | grep "Loaded Configuration" покажет активный файл для CLI, для FPM смотрите конфиг пула.&lt;/p&gt;

&lt;p&gt;Перезапуск:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
systemctl restart php8.2-fpm

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Проверка:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
php &lt;span class="nt"&gt;-i&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;opcache.enable

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ожидаем:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
opcache.enable =&amp;gt; On =&amp;gt; On

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;2️⃣ Настройка PHP-FPM (CPU не должен кипеть)&lt;/p&gt;

&lt;p&gt;Полный вариант с проверкой памяти: сниппет «PHP-FPM pool (pm dynamic)». Ниже — ключевые параметры.&lt;/p&gt;

&lt;p&gt;Файл:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
/etc/php/8.2/fpm/pool.d/www.conf

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Меняем pm режим:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;
&lt;span class="py"&gt;pm&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;dynamic&lt;/span&gt;

&lt;span class="err"&gt;pm.max\&lt;/span&gt;&lt;span class="py"&gt;_children&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;20&lt;/span&gt;

&lt;span class="err"&gt;pm.start\&lt;/span&gt;&lt;span class="py"&gt;_servers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;4&lt;/span&gt;

&lt;span class="err"&gt;pm.min\_spare\&lt;/span&gt;&lt;span class="py"&gt;_servers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;4&lt;/span&gt;

&lt;span class="err"&gt;pm.max\_spare\&lt;/span&gt;&lt;span class="py"&gt;_servers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;8&lt;/span&gt;

&lt;span class="err"&gt;pm.max\&lt;/span&gt;&lt;span class="py"&gt;_requests&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;500&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;pm.max_requests — после скольких запросов воркер перезапускается; снижает риск утечек памяти в долгоживущих процессах. 500–1000 нормально для WordPress.&lt;/p&gt;

&lt;p&gt;Как считать:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;max_children ≈ RAM / 60MB&lt;/li&gt;
&lt;li&gt;Если 2GB RAM → ~30 процессов максимум&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Проверка нагрузки:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
ps &lt;span class="nt"&gt;--no-headers&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"rss,cmd"&lt;/span&gt; &lt;span class="nt"&gt;-C&lt;/span&gt; php-fpm8.2 | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{ sum+=$1 } END { print sum/1024 " MB" }'&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;3️⃣ Включаем fastcgi_cache в Nginx&lt;/p&gt;

&lt;p&gt;Это ключевой момент. Готовый конфиг с bypass для админки: сниппет «Nginx fastcgi_cache для WordPress».&lt;/p&gt;

&lt;p&gt;Файл:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
/etc/nginx/nginx.conf

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Добавляем в http{}:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;
&lt;span class="k"&gt;fastcgi\_cache\_path&lt;/span&gt; &lt;span class="n"&gt;/var/cache/nginx&lt;/span&gt; &lt;span class="s"&gt;levels=1:2&lt;/span&gt; &lt;span class="s"&gt;keys&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_zone=WORDPRESS:100m&lt;/span&gt; &lt;span class="s"&gt;inactive=60m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;fastcgi\_cache\_key&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$scheme$request&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_method&lt;/span&gt;&lt;span class="nv"&gt;$host$request&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_uri"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Создаём директорию:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /var/cache/nginx

&lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; www-data:www-data /var/cache/nginx

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Размер зоны keys_zone=WORDPRESS:100m — это не место на диске, а объём ключей в памяти. 100m хватает на десятки тысяч URL. Реальный кеш лежит в /var/cache/nginx; убедитесь, что на разделе достаточно места (гигабайт и больше для активного сайта). Параметр inactive=60m — удалять ключ из кеша, если к нему не обращались 60 минут.&lt;/p&gt;




&lt;p&gt;4️⃣ Настройка server{} для WordPress&lt;/p&gt;

&lt;p&gt;Файл сайта:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
/etc/nginx/sites-available/example.com

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;В location ~ .php$ добавляем:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;
&lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$skip&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_cache&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_method&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;POST)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="kn"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$skip&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_cache&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_string&lt;/span&gt; &lt;span class="s"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;"")&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="kn"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$skip&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_cache&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_uri&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt;&lt;span class="sr"&gt;\*&lt;/span&gt; &lt;span class="s"&gt;"/wp-admin/|/xmlrpc.php|wp-login.php")&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="kn"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$skip&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_cache&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$http&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_cookie&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt;&lt;span class="sr"&gt;\*&lt;/span&gt; &lt;span class="s"&gt;"wordpress&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_logged&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_in")&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="kn"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$skip&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_cache&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;location&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt; &lt;span class="sr"&gt;\.php$&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="kn"&gt;include&lt;/span&gt; &lt;span class="s"&gt;fastcgi&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;fastcgi\_pass&lt;/span&gt; &lt;span class="s"&gt;unix:/run/php/php8.2-fpm.sock&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;fastcgi\_param&lt;/span&gt; &lt;span class="s"&gt;SCRIPT&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_FILENAME&lt;/span&gt; &lt;span class="nv"&gt;$document&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_root&lt;/span&gt;&lt;span class="nv"&gt;$fastcgi&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_script&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;fastcgi\_cache&lt;/span&gt; &lt;span class="s"&gt;WORDPRESS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;fastcgi\_cache\_valid&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="mi"&gt;60m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;fastcgi\_cache\_bypass&lt;/span&gt; &lt;span class="nv"&gt;$skip&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;fastcgi\_no\_cache&lt;/span&gt; &lt;span class="nv"&gt;$skip&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;add\_header&lt;/span&gt; &lt;span class="s"&gt;X-FastCGI-Cache&lt;/span&gt; &lt;span class="nv"&gt;$upstream&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_cache&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_status&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Перезапуск:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
systemctl restart nginx

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Проверка результата&lt;/p&gt;

&lt;p&gt;Проверка TTFB&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
curl &lt;span class="nt"&gt;-I&lt;/span&gt; https://example.com

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;В заголовках должно быть:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
X-FastCGI-Cache: MISS

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Повторный запрос:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
X-FastCGI-Cache: HIT

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Проверка времени:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
curl &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"TTFB: %{time&lt;/span&gt;&lt;span class="se"&gt;\_&lt;/span&gt;&lt;span class="s2"&gt;starttransfer}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; https://example.com

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ожидаемый результат:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
TTFB: 0.112341

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CPU должен упасть минимум в 2–4 раза.&lt;/p&gt;

&lt;p&gt;Краткий чеклист:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OPcache: php -i | grep opcache.enable → On&lt;/li&gt;
&lt;li&gt;PHP-FPM: в pool.d/&lt;a href="http://www.conf" rel="noopener noreferrer"&gt;www.conf&lt;/a&gt; стоит pm = dynamic, pm.max_children по памяти&lt;/li&gt;
&lt;li&gt;Nginx: в ответе есть заголовок X-FastCGI-Cache, при повторном запросе — HIT&lt;/li&gt;
&lt;li&gt;TTFB: для закешированной главной — меньше 0,3 сек (лучше 0,05–0,15)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Если хотя бы один пункт не выполняется — возвращайтесь к соответствующему шагу выше. На проде после любых изменений конфига делайте nginx -t перед systemctl reload nginx.&lt;/p&gt;




&lt;p&gt;Если не работает&lt;/p&gt;

&lt;p&gt;1️⃣ Нет заголовка X-FastCGI-Cache&lt;/p&gt;

&lt;p&gt;→ location блок не применяется.&lt;/p&gt;

&lt;p&gt;Проверь:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
nginx &lt;span class="nt"&gt;-T&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;fastcgi&lt;span class="se"&gt;\_&lt;/span&gt;cache

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;2️⃣ Всегда BYPASS&lt;/p&gt;

&lt;p&gt;→ Cookie не даёт кешировать.&lt;/p&gt;

&lt;p&gt;Проверь:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
curl &lt;span class="nt"&gt;-I&lt;/span&gt; https://example.com | &lt;span class="nb"&gt;grep &lt;/span&gt;Set-Cookie

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;3️⃣ OPcache не даёт прироста&lt;/p&gt;

&lt;p&gt;→ validate_timestamps включён.&lt;/p&gt;

&lt;p&gt;Проверь:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
php &lt;span class="nt"&gt;-i&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;validate&lt;span class="se"&gt;\_&lt;/span&gt;timestamps

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Должно быть 0 в проде. После деплоя кода делайте systemctl reload php8.2-fpm, чтобы OPcache подхватил новые файлы.&lt;/p&gt;

&lt;p&gt;4️⃣ Кеш не создаётся (всегда MISS)&lt;/p&gt;

&lt;p&gt;→ путь к кешу неверный или нет прав.&lt;/p&gt;

&lt;p&gt;Проверь:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /var/cache/nginx

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Владелец должен быть www-data (или пользователь nginx). Если директории нет — создай и выдай права, как в шаге 3 выше, затем systemctl restart nginx.&lt;/p&gt;




&lt;p&gt;Типичные ошибки&lt;/p&gt;

&lt;p&gt;❌ OPcache 64MB&lt;/p&gt;

&lt;p&gt;Причина: дефолт Debian&lt;/p&gt;

&lt;p&gt;Решение: минимум 256MB&lt;/p&gt;




&lt;p&gt;❌ pm = ondemand&lt;/p&gt;

&lt;p&gt;Причина: экономия памяти&lt;/p&gt;

&lt;p&gt;Проблема: запуск процессов под нагрузкой&lt;/p&gt;

&lt;p&gt;Решение: dynamic&lt;/p&gt;




&lt;p&gt;❌ Кеширование админки&lt;/p&gt;

&lt;p&gt;Причина: нет bypass&lt;/p&gt;

&lt;p&gt;Решение: skip_cache условия&lt;/p&gt;




&lt;p&gt;❌ Кеш не чистится при деплое&lt;/p&gt;

&lt;p&gt;Причина: validate_timestamps=0&lt;/p&gt;

&lt;p&gt;Решение: после деплоя:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
systemctl reload php8.2-fpm

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Где применять&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Production VPS&lt;/li&gt;
&lt;li&gt;Nginx + PHP-FPM&lt;/li&gt;
&lt;li&gt;Без Docker&lt;/li&gt;
&lt;li&gt;CI/CD с деплоем через git pull&lt;/li&gt;
&lt;li&gt;WordPress 6.x&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Если используешь Docker — логика та же, но путь к сокету другой. Основы Nginx — в Nginx: основы веб-сервера. Дальнейшая оптимизация фронта (LCP, INP, кеш) — WordPress и Core Web Vitals.&lt;/p&gt;




&lt;p&gt;После обновления кода или темы&lt;/p&gt;

&lt;p&gt;При opcache.validate_timestamps=0 OPcache не перечитывает файлы сам. После деплоя (git pull, загрузка обновлённой темы или плагинов) выполните:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
systemctl reload php8.2-fpm

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Так PHP-FPM перезапустит воркеры и подхватит новый код. При необходимости сбросьте fastcgi_cache: удалите содержимое /var/cache/nginx или перезапустите Nginx. Для инвалидации только части кеша в Nginx нужна отдельная настройка (например, purge по ключу).&lt;/p&gt;




&lt;p&gt;Итог&lt;/p&gt;

&lt;p&gt;Если &lt;strong&gt;wordpress долго загружается nginx php-fpm&lt;/strong&gt; , проблема почти никогда не в “тяжёлом WordPress”.&lt;/p&gt;

&lt;p&gt;Проблема в:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;выключенном OPcache&lt;/li&gt;
&lt;li&gt;неправильном pm&lt;/li&gt;
&lt;li&gt;отсутствии fastcgi_cache&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;После этих трёх шагов:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TTFB меньше 300 мс&lt;/li&gt;
&lt;li&gt;CPU стабилен&lt;/li&gt;
&lt;li&gt;Сайт ощущается быстрым&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;WordPress может работать быстро. Просто стек должен быть настроен как прод, а не как shared-хостинг 2012 года. Следуйте шагам по порядку и проверяйте результат после каждого этапа.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://viku-lov.ru/blog/wordpress-dolgo-zagruzhaetsya-nginx-php-fpm-ttfb" rel="noopener noreferrer"&gt;Read more on viku-lov.ru&lt;/a&gt;&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>nginx</category>
      <category>phpfpm</category>
      <category>opcache</category>
    </item>
    <item>
      <title>WordPress: как настроить системный cron вместо WP-Cron</title>
      <dc:creator>Андрей Викулов (VProger)</dc:creator>
      <pubDate>Fri, 13 Feb 2026 22:51:40 +0000</pubDate>
      <link>https://forem.com/_vproger_/wordpress-kak-nastroit-sistiemnyi-cron-vmiesto-wp-cron-12p</link>
      <guid>https://forem.com/_vproger_/wordpress-kak-nastroit-sistiemnyi-cron-vmiesto-wp-cron-12p</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw5pev9i640c7dqynlhul.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw5pev9i640c7dqynlhul.png" alt="WordPress: как настроить системный cron вместо WP-Cron" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;WordPress: как настроить системный cron вместо WP-Cron&lt;/p&gt;

&lt;p&gt;Если у вас &lt;strong&gt;wordpress wp cron не выполняется&lt;/strong&gt; , не отправляются письма, не работает импорт, не публикуются отложенные записи — вы упёрлись в архитектурное ограничение WP-Cron.&lt;/p&gt;

&lt;p&gt;В продакшене на VPS с nginx это лечится правильно: отключаем псевдо-cron WordPress и переводим задачи на системный cron Linux.&lt;/p&gt;

&lt;p&gt;В конце получите:&lt;/p&gt;

&lt;p&gt;✔ WP-Cron отключён&lt;/p&gt;

&lt;p&gt;✔ системный cron настроен&lt;/p&gt;

&lt;p&gt;✔ задачи выполняются стабильно и предсказуемо&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Сниппеты по статье:&lt;/strong&gt; Отключение WP-Cron (wp-config) · Crontab для wp-cron.php · Проверка событий (wp cron event list) · Вызов wp-cron по HTTP (curl)&lt;/p&gt;




&lt;p&gt;В чём проблема&lt;/p&gt;

&lt;p&gt;Реальный симптом&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Заказы WooCommerce не обрабатываются&lt;/li&gt;
&lt;li&gt;wp_mail() не отправляет письма&lt;/li&gt;
&lt;li&gt;Отложенные посты зависают&lt;/li&gt;
&lt;li&gt;Импорт через wp_schedule_event не запускается&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Почему это происходит&lt;/p&gt;

&lt;p&gt;WP-Cron — это &lt;strong&gt;не настоящий cron&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Он запускается только при HTTP-запросе к сайту.&lt;/p&gt;

&lt;p&gt;Механика (по документации WordPress):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;При загрузке страницы WordPress проверяет опцию cron.&lt;/li&gt;
&lt;li&gt;Если есть просроченные задачи — выполняет wp-cron.php.&lt;/li&gt;
&lt;li&gt;Если трафика нет — задачи не выполняются.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;На проде это приводит к:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;задержкам&lt;/li&gt;
&lt;li&gt;гонкам процессов&lt;/li&gt;
&lt;li&gt;зависанию задач при высокой нагрузке&lt;/li&gt;
&lt;li&gt;полному отсутствию выполнения на малопосещаемых сайтах&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Как проверить, что WP-Cron не работает&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Проверяем запланированные события:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
wp cron event list

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Если видите старые timestamps — задачи не выполняются.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Проверяем вручную:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
curl https://example.com/wp-cron.php?doing&lt;span class="se"&gt;\_&lt;/span&gt;wp&lt;span class="se"&gt;\_&lt;/span&gt;cron

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Если ничего не происходит — cron завис.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Проверяем опцию:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
wp option get cron

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Если там старые даты — всё очевидно.&lt;/p&gt;




&lt;p&gt;Рабочее решение&lt;/p&gt;

&lt;p&gt;Шаг 1. Отключаем WP-Cron&lt;/p&gt;

&lt;p&gt;Готовый фрагмент и пояснения: сниппет «Отключение WP-Cron в wp-config». Ниже — кратко.&lt;/p&gt;

&lt;p&gt;Файл:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
/var/www/site/public\_html/wp-config.php

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Добавляем строку:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;
&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DISABLE\_WP\_CRON'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Это официально рекомендуемый способ (WordPress documentation).&lt;/p&gt;

&lt;p&gt;Важно:&lt;/p&gt;

&lt;p&gt;Добавлять &lt;strong&gt;до строки&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;
&lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;_That&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stop&lt;/span&gt; &lt;span class="n"&gt;editing&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Шаг 2. Проверяем путь к PHP&lt;/p&gt;

&lt;p&gt;На VPS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
which php

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Обычно:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
/usr/bin/php

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Если используется PHP-FPM 8.x, путь может быть:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
/usr/bin/php8.2

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Шаг 3. Настраиваем системный cron&lt;/p&gt;

&lt;p&gt;Полная строка и варианты расписания: сниппет «Crontab для wp-cron.php». Ниже — базовая настройка.&lt;/p&gt;

&lt;p&gt;Открываем cron текущего пользователя:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
crontab &lt;span class="nt"&gt;-e&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Добавляем строку:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
_/5_ &lt;span class="se"&gt;\*&lt;/span&gt; /usr/bin/php /var/www/site/public&lt;span class="se"&gt;\_&lt;/span&gt;html/wp-cron.php &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null 2&amp;gt;&amp;amp;1

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Разбор:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;*/5 — каждые 5 минут&lt;/li&gt;
&lt;li&gt;полный путь к php&lt;/li&gt;
&lt;li&gt;полный путь к wp-cron.php&lt;/li&gt;
&lt;li&gt;вывод в null (без мусора в почту)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Если сайт крупный — можно поставить раз в минуту:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
- /usr/bin/php /var/www/site/public&lt;span class="se"&gt;\_&lt;/span&gt;html/wp-cron.php &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null 2&amp;gt;&amp;amp;1

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Альтернатива через HTTP (если нет CLI PHP)&lt;/p&gt;

&lt;p&gt;Сниппет «Вызов wp-cron по HTTP (curl)»:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
_/5_ &lt;span class="se"&gt;\*&lt;/span&gt; curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://example.com/wp-cron.php?doing&lt;span class="se"&gt;\_&lt;/span&gt;wp&lt;span class="se"&gt;\_&lt;/span&gt;cron &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null 2&amp;gt;&amp;amp;1

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Но CLI-вариант надёжнее.&lt;/p&gt;




&lt;p&gt;Шаг 4. Проверяем, что cron добавился&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
crontab &lt;span class="nt"&gt;-l&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Должна быть ваша строка.&lt;/p&gt;




&lt;p&gt;Проверка результата&lt;/p&gt;

&lt;p&gt;Проверка выполнения задач&lt;/p&gt;

&lt;p&gt;Через 5–10 минут (сниппет «Проверка событий wp cron event list»):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
wp cron event list

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Временные метки должны обновляться.&lt;/p&gt;




&lt;p&gt;Проверка логов nginx&lt;/p&gt;

&lt;p&gt;Файл:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
/var/log/nginx/access.log

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Если используете HTTP-вариант — должны появляться обращения к wp-cron.php.&lt;/p&gt;




&lt;p&gt;Принудительный запуск&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
wp cron event run &lt;span class="nt"&gt;--due-now&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Если выполняется без ошибок — система рабочая.&lt;/p&gt;




&lt;p&gt;Диагностика, если не работает&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Проверяем пользователя cron
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
ps aux | &lt;span class="nb"&gt;grep &lt;/span&gt;cron

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Если cron работает от root, а сайт принадлежит www-data — могут быть проблемы с правами.&lt;/p&gt;

&lt;p&gt;Лучше добавить cron от того же пользователя, под которым работает сайт.&lt;/p&gt;




&lt;ol&gt;
&lt;li&gt;Проверяем права доступа
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; wp-cron.php

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Файл должен быть читаемым для пользователя cron.&lt;/p&gt;




&lt;ol&gt;
&lt;li&gt;Проверяем SELinux (если включён)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;На CentOS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
getenforce

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Если Enforcing — временно проверить:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
setenforce 0

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Если после этого заработало — проблема в политике.&lt;/p&gt;




&lt;p&gt;Типичные ошибки&lt;/p&gt;

&lt;p&gt;❌ Оставили WP-Cron включённым&lt;/p&gt;

&lt;p&gt;Причина: забыли добавить DISABLE_WP_CRON.&lt;/p&gt;

&lt;p&gt;Результат: двойной запуск задач.&lt;/p&gt;

&lt;p&gt;Исправление: проверить wp-config.php.&lt;/p&gt;




&lt;p&gt;❌ Указан неверный путь к PHP&lt;/p&gt;

&lt;p&gt;Причина: в системе несколько версий.&lt;/p&gt;

&lt;p&gt;Проверка:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
php &lt;span class="nt"&gt;-v&lt;/span&gt;

which php

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Исправление: указать полный корректный путь.&lt;/p&gt;




&lt;p&gt;❌ Неверный путь к wp-cron.php&lt;/p&gt;

&lt;p&gt;Причина: указали относительный путь.&lt;/p&gt;

&lt;p&gt;Нужно:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
/var/www/site/public&lt;span class="se"&gt;\_&lt;/span&gt;html/wp-cron.php

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;❌ Cron добавлен, но не выполняется&lt;/p&gt;

&lt;p&gt;Проверить:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
systemctl status cron

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;или&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
systemctl status crond

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Если не запущен:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
systemctl start cron

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Где применять&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✔ Production VPS&lt;/li&gt;
&lt;li&gt;✔ Nginx&lt;/li&gt;
&lt;li&gt;✔ WooCommerce&lt;/li&gt;
&lt;li&gt;✔ Импортёры&lt;/li&gt;
&lt;li&gt;✔ Интеграции&lt;/li&gt;
&lt;li&gt;✔ CI/CD деплой после миграции&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Не применять:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Локальная разработка&lt;/li&gt;
&lt;li&gt;Docker-контейнер без supervisor&lt;/li&gt;
&lt;li&gt;Shared hosting без доступа к cron&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Когда это критично&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Малый трафик&lt;/li&gt;
&lt;li&gt;Email-воронки&lt;/li&gt;
&lt;li&gt;Платежи&lt;/li&gt;
&lt;li&gt;REST-интеграции&lt;/li&gt;
&lt;li&gt;Webhook-обработчики&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Если wp cron не выполняется — это не баг WordPress.&lt;/p&gt;

&lt;p&gt;Это неправильная эксплуатация.&lt;/p&gt;




&lt;p&gt;Дополнительные материалы&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.wordpress.org/plugins/cron/" rel="noopener noreferrer"&gt;https://developer.wordpress.org/plugins/cron/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.wordpress.org/reference/functions/wp_schedule_event/" rel="noopener noreferrer"&gt;https://developer.wordpress.org/reference/functions/wp_schedule_event/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wordpress.org/support/article/cron/" rel="noopener noreferrer"&gt;https://wordpress.org/support/article/cron/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Итог&lt;/p&gt;

&lt;p&gt;Если &lt;strong&gt;wordpress wp cron не выполняется&lt;/strong&gt; , не надо искать плагины-костыли.&lt;/p&gt;

&lt;p&gt;Правильный прод-подход:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Отключить WP-Cron&lt;/li&gt;
&lt;li&gt;Настроить системный cron&lt;/li&gt;
&lt;li&gt;Проверить выполнение&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;После этого:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;письма отправляются&lt;/li&gt;
&lt;li&gt;импорт работает&lt;/li&gt;
&lt;li&gt;отложенные записи публикуются&lt;/li&gt;
&lt;li&gt;задачи выполняются стабильно&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;И главное — поведение становится предсказуемым.&lt;/p&gt;

&lt;p&gt;В продакшене псевдо-cron не место.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://viku-lov.ru/blog/wordpress-wp-cron-ne-vypolnyaetsya-nastroit-systemnyj-cron" rel="noopener noreferrer"&gt;Read more on viku-lov.ru&lt;/a&gt;&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>wpcron</category>
      <category>nginx</category>
      <category>vps</category>
    </item>
    <item>
      <title>Apache httpd и PHP-FPM: модули и расширения для продакшена (с учётом Bitrix)</title>
      <dc:creator>Андрей Викулов (VProger)</dc:creator>
      <pubDate>Thu, 12 Feb 2026 05:29:51 +0000</pubDate>
      <link>https://forem.com/_vproger_/apache-httpd-i-php-fpm-moduli-i-rasshirieniia-dlia-prodakshiena-s-uchiotom-bitrix-1c6</link>
      <guid>https://forem.com/_vproger_/apache-httpd-i-php-fpm-moduli-i-rasshirieniia-dlia-prodakshiena-s-uchiotom-bitrix-1c6</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fri9x4n21by47rx3k6a6n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fri9x4n21by47rx3k6a6n.png" alt="Apache httpd и PHP-FPM: модули и расширения для продакшена (с учётом Bitrix)" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Apache httpd и PHP-FPM: модули и расширения для продакшена (с учётом Bitrix)&lt;/p&gt;

&lt;p&gt;Если вы работаете с Bitrix CMS, Laravel или чистым PHP — рано или поздно придётся разбираться, какие модули Apache и расширения PHP действительно нужны, а какие лишь мусор «на всякий случай».&lt;/p&gt;

&lt;p&gt;Разберём:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;полезные модули Apache httpd;&lt;/li&gt;
&lt;li&gt;критичные расширения PHP для PHP-FPM;&lt;/li&gt;
&lt;li&gt;что обязательно для Bitrix;&lt;/li&gt;
&lt;li&gt;что даёт прирост производительности;&lt;/li&gt;
&lt;li&gt;что лучше отключить.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Без теории ради теории. Только то, что реально влияет на прод.&lt;/p&gt;




&lt;p&gt;Часть 1. Apache httpd — что реально нужно&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://upload.wikimedia.org/wikipedia/commons/1/10/Apache_HTTP_server_logo_%282019-present%29.svg" rel="noopener noreferrer"&gt;Логотип Apache HTTP Server&lt;/a&gt; (Wikipedia)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.researchgate.net/publication/229037429/figure/fig1/AS%3A300761419403271%401448718518723/Apache-worker-MPM-architecture.png" rel="noopener noreferrer"&gt;Архитектура Apache worker MPM&lt;/a&gt; (ResearchGate)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://httpd.apache.org/docs/trunk/images/rewrite_backreferences.png" rel="noopener noreferrer"&gt;mod_rewrite: обратные ссылки&lt;/a&gt; (документация Apache)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://httpd.apache.org/docs/trunk/images/syntax_rewriterule.png" rel="noopener noreferrer"&gt;Синтаксис RewriteRule&lt;/a&gt; (документация Apache)&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;MPM: prefork vs event&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;❌ Prefork (устаревший подход)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;каждый запрос = отдельный процесс&lt;/li&gt;
&lt;li&gt;много памяти&lt;/li&gt;
&lt;li&gt;актуален только при использовании mod_php&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Если вы используете PHP-FPM (а так и стоит делать), prefork не нужен.&lt;/p&gt;

&lt;p&gt;✅ MPM event (рекомендуется)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;потоковая модель&lt;/li&gt;
&lt;li&gt;меньше памяти&lt;/li&gt;
&lt;li&gt;лучше под нагрузкой&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Включение:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
a2dismod mpm&lt;span class="se"&gt;\_&lt;/span&gt;prefork

a2enmod mpm&lt;span class="se"&gt;\_&lt;/span&gt;event

systemctl restart apache2

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Для Bitrix:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;На нагруженных проектах event + PHP-FPM даёт ощутимо лучшую стабильность.&lt;/p&gt;




&lt;ol&gt;
&lt;li&gt;mod_rewrite — без него никуда&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Bitrix, Laravel, WordPress — все используют rewrite для ЧПУ и маршрутизации. Модуль использует PCRE-совместимые правила; описание директив — в &lt;a href="https://httpd.apache.org/docs/2.4/mod/mod_rewrite.html" rel="noopener noreferrer"&gt;официальной документации Apache mod_rewrite&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Базовый пример&lt;/strong&gt; (запросы к несуществующим файлам и каталогам уходят в index.php):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nl"&gt;IfModule&lt;/span&gt;&lt;span class="sr"&gt; mod\_rewrite.c&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;
&lt;span class="nc"&gt;RewriteEngine&lt;/span&gt; &lt;span class="ss"&gt;On&lt;/span&gt;

&lt;span class="nc"&gt;RewriteCond&lt;/span&gt; %{REQUEST\_FILENAME} !-f

&lt;span class="nc"&gt;RewriteCond&lt;/span&gt; %{REQUEST\_FILENAME} !-d

&lt;span class="nc"&gt;RewriteRule&lt;/span&gt; ^(.\*)$ /index.php [L]

&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nl"&gt;IfModule&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;!-f и !-d — условие: «если нет такого файла и нет такой директории» (см. &lt;a href="https://httpd.apache.org/docs/2.4/mod/mod_rewrite.html#rewritecond" rel="noopener noreferrer"&gt;RewriteCond&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;Флаг &lt;a href="https://dev.toLast"&gt;L&lt;/a&gt; — остановить обработку правил после срабатывания.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Если приложение в подкаталоге&lt;/strong&gt; (например, /bitrix-site/), нужен RewriteBase, иначе подстановка может дать неверный путь:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;
&lt;span class="nc"&gt;RewriteEngine&lt;/span&gt; &lt;span class="ss"&gt;On&lt;/span&gt;

&lt;span class="nc"&gt;RewriteBase&lt;/span&gt; /bitrix-site/

&lt;span class="nc"&gt;RewriteCond&lt;/span&gt; %{REQUEST\_FILENAME} !-f

&lt;span class="nc"&gt;RewriteCond&lt;/span&gt; %{REQUEST\_FILENAME} !-d

&lt;span class="nc"&gt;RewriteRule&lt;/span&gt; ^(.\*)$ index.php [L]

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Без mod_rewrite ЧПУ и «красивые» URL в Bitrix и других CMS не работают.&lt;/p&gt;




&lt;ol&gt;
&lt;li&gt;mod_ssl — HTTPS обязателен&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Шифрование по TLS обеспечивает &lt;a href="https://httpd.apache.org/docs/2.4/mod/mod_ssl.html" rel="noopener noreferrer"&gt;mod_ssl&lt;/a&gt; (опирается на OpenSSL). SSLv2 не поддерживается; для продакшена оставляют только современные протоколы.&lt;/p&gt;

&lt;p&gt;Проверка, что модуль загружен:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
apachectl &lt;span class="nt"&gt;-M&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;ssl

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Минимальный фрагмент виртуального хоста с Let's Encrypt (директивы &lt;a href="https://httpd.apache.org/docs/2.4/mod/mod_ssl.html#sslcertificatefile" rel="noopener noreferrer"&gt;SSLCertificateFile&lt;/a&gt;, &lt;a href="https://httpd.apache.org/docs/2.4/mod/mod_ssl.html#sslcertificatekeyfile" rel="noopener noreferrer"&gt;SSLCertificateKeyFile&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nl"&gt;VirtualHost&lt;/span&gt;&lt;span class="sr"&gt; \*:443&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;
&lt;span class="nc"&gt;ServerName&lt;/span&gt; example.com

&lt;span class="nc"&gt;DocumentRoot&lt;/span&gt; /var/www/example.com

&lt;span class="nc"&gt;SSLEngine&lt;/span&gt; &lt;span class="ss"&gt;on&lt;/span&gt;

&lt;span class="nc"&gt;SSLCertificateFile&lt;/span&gt; /etc/letsencrypt/live/example.com/fullchain.pem

&lt;span class="nc"&gt;SSLCertificateKeyFile&lt;/span&gt; /etc/letsencrypt/live/example.com/privkey.pem

&lt;span class="err"&gt;Только&lt;/span&gt; &lt;span class="nc"&gt;TLS&lt;/span&gt; 1.2 и 1.3 (SSLv3 отключён в современных сборках)

&lt;span class="nc"&gt;SSLProtocol&lt;/span&gt; &lt;span class="ss"&gt;all&lt;/span&gt; -SSLv3 -TLSv1 -TLSv1.1

&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nl"&gt;VirtualHost&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Рекомендуется также настроить &lt;a href="https://httpd.apache.org/docs/2.4/mod/mod_ssl.html#sslciphersuite" rel="noopener noreferrer"&gt;SSLCipherSuite&lt;/a&gt; под актуальные рекомендации по безопасности. Bitrix без HTTPS — минус для SEO и безопасности; поисковики и браузеры помечают такие сайты как небезопасные.&lt;/p&gt;




&lt;ol&gt;
&lt;li&gt;mod_headers — контроль кэша и безопасности&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://httpd.apache.org/docs/2.4/mod/mod_headers.html" rel="noopener noreferrer"&gt;mod_headers&lt;/a&gt; позволяет задавать и изменять HTTP-заголовки ответа. Для продакшена обычно выставляют заголовки безопасности и при необходимости — кэширование статики.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Безопасность (рекомендуемый минимум):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;
&lt;span class="nc"&gt;Header&lt;/span&gt; &lt;span class="ss"&gt;always&lt;/span&gt; &lt;span class="ss"&gt;set&lt;/span&gt; X-Content-Type-Options "nosniff"

&lt;span class="nc"&gt;Header&lt;/span&gt; &lt;span class="ss"&gt;always&lt;/span&gt; &lt;span class="ss"&gt;set&lt;/span&gt; X-Frame-Options "SAMEORIGIN"

&lt;span class="nc"&gt;Header&lt;/span&gt; &lt;span class="ss"&gt;always&lt;/span&gt; &lt;span class="ss"&gt;set&lt;/span&gt; X-XSS-Protection "1; mode=block"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Кэширование статики Bitrix&lt;/strong&gt; (картинки, CSS/JS в /upload/, /local/): можно ограничить время кэша в браузере, чтобы не перегружать сервер повторными запросами к тем же файлам:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nl"&gt;IfModule&lt;/span&gt;&lt;span class="sr"&gt; mod\_headers.c&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nl"&gt;FilesMatch&lt;/span&gt;&lt;span class="sr"&gt; "\.(ico|webp|jpe?g|png|gif|css|js|woff2?)$"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;
&lt;span class="nc"&gt;Header&lt;/span&gt; &lt;span class="ss"&gt;set&lt;/span&gt; Cache-Control "public, max-age=2592000"

&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nl"&gt;FilesMatch&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nl"&gt;IfModule&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;max-age=2592000 — 30 дней; для часто меняющихся ресурсов значение уменьшают.&lt;/p&gt;




&lt;ol&gt;
&lt;li&gt;mod_deflate / mod_brotli — сжатие&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://httpd.apache.org/docs/2.4/mod/mod_deflate.html" rel="noopener noreferrer"&gt;mod_deflate&lt;/a&gt; сжимает ответы по алгоритму DEFLATE (gzip). Поддерживается только gzip-кодирование для совместимости со старыми клиентами. Рекомендуемая конфигурация из документации Apache — сжимать только указанные MIME-типы:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nl"&gt;IfModule&lt;/span&gt;&lt;span class="sr"&gt; mod\_deflate.c&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;
&lt;span class="nc"&gt;AddOutputFilterByType&lt;/span&gt; DEFLATE text/html text/plain text/xml text/css text/javascript application/javascript application/json

&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nl"&gt;IfModule&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Не сжимайте уже сжатое (например, изображения в форматах с сжатием). На TLS-соединениях учитывайте риски класса атак &lt;a href="https://httpd.apache.org/docs/2.4/mod/mod_deflate.html" rel="noopener noreferrer"&gt;BREACH&lt;/a&gt; при сжатии конфиденциального контента — для статики и типовых HTML/CSS/JS такая настройка безопасна.&lt;/p&gt;

&lt;p&gt;Brotli (лучшая степень сжатия при сопоставимой скорости) — если в системе есть mod_brotli:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
a2enmod brotli

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Сжатие даёт заметное уменьшение трафика и ускорение загрузки страниц без изменения кода приложения.&lt;/p&gt;




&lt;ol&gt;
&lt;li&gt;mod_expires — кэширование статики&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://httpd.apache.org/docs/2.4/mod/mod_expires.html" rel="noopener noreferrer"&gt;mod_expires&lt;/a&gt; выставляет заголовки Expires и Cache-Control: max-age по типу контента. Базовое время можно задать через access (от момента запроса) или modification (от даты изменения файла). Подробнее — в &lt;a href="https://httpd.apache.org/docs/2.4/mod/mod_expires.html#expiresbytype" rel="noopener noreferrer"&gt;документации ExpiresByType&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Пример для типичной статики Bitrix (картинки, стили, скрипты):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nl"&gt;IfModule&lt;/span&gt;&lt;span class="sr"&gt; mod\_expires.c&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;
&lt;span class="nc"&gt;ExpiresActive&lt;/span&gt; &lt;span class="ss"&gt;On&lt;/span&gt;

&lt;span class="nc"&gt;ExpiresDefault&lt;/span&gt; "access plus 1 month"

&lt;span class="nc"&gt;ExpiresByType&lt;/span&gt; image/webp "access plus 1 year"

&lt;span class="nc"&gt;ExpiresByType&lt;/span&gt; image/jpeg "access plus 1 year"

&lt;span class="nc"&gt;ExpiresByType&lt;/span&gt; image/png "access plus 1 year"

&lt;span class="nc"&gt;ExpiresByType&lt;/span&gt; image/gif "access plus 1 year"

&lt;span class="nc"&gt;ExpiresByType&lt;/span&gt; text/css "access plus 1 month"

&lt;span class="nc"&gt;ExpiresByType&lt;/span&gt; application/javascript "access plus 1 month"

&lt;span class="nc"&gt;ExpiresByType&lt;/span&gt; font/woff2 "access plus 1 year"

&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nl"&gt;IfModule&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;access plus 1 month означает: браузер может хранить копию 1 месяц с момента последнего запроса. Bitrix отдаёт много статики из /upload/ и шаблонов — корректные Expires снижают число запросов к серверу.&lt;/p&gt;




&lt;p&gt;Часть 2. PHP-FPM — расширения, которые реально нужны&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://upload.wikimedia.org/wikipedia/commons/thumb/2/27/PHP-logo.svg/3840px-PHP-logo.svg.png" rel="noopener noreferrer"&gt;Логотип PHP&lt;/a&gt; (Wikipedia)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://a.storyblok.com/f/294279/51f620ac36/wp-2.png" rel="noopener noreferrer"&gt;Схема работы PHP с веб-сервером&lt;/a&gt; (Storyblok)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://accesto.com/blog/static/6dec240d9b76e9dc270d1a5f32814bd7/5a190/importance-of-opcode-caching.png" rel="noopener noreferrer"&gt;Важность кэширования opcode&lt;/a&gt; (Accesto)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://media.licdn.com/dms/image/v2/D5612AQEz15QqVWFUag/article-inline_image-shrink_1000_1488/article-inline_image-shrink_1000_1488/0/1723271019081?e=2147483647&amp;amp;t=XiTApJaTfHTnMaXgv62qVFjJhDaOf2_922gZyQ4iGVk&amp;amp;v=beta" rel="noopener noreferrer"&gt;Производительность и кэширование&lt;/a&gt; (LinkedIn)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Переходим к PHP.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;OPcache — обязательно&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;OPcache кэширует скомпилированный PHP-код (opcode) в памяти и значительно снижает нагрузку на CPU. Без него продакшен запускать не рекомендуется; настройка описана в &lt;a href="https://www.php.net/manual/en/book.opcache.php" rel="noopener noreferrer"&gt;официальной документации PHP&lt;/a&gt; и в &lt;a href="https://www.php.net/manual/en/opcache.configuration.php" rel="noopener noreferrer"&gt;Runtime Configuration&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Минимальная конфигурация для продакшена (в php.ini или в пуле PHP-FPM):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;
&lt;span class="py"&gt;opcache.enable&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;

&lt;span class="err"&gt;opcache.memory\&lt;/span&gt;&lt;span class="py"&gt;_consumption&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;256&lt;/span&gt;

&lt;span class="err"&gt;opcache.max\_accelerated\&lt;/span&gt;&lt;span class="py"&gt;_files&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;20000&lt;/span&gt;

&lt;span class="err"&gt;opcache.validate\&lt;/span&gt;&lt;span class="py"&gt;_timestamps&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;validate_timestamps=0 — не проверять изменение файлов на диске (максимальная производительность). После выката кода перезапускайте PHP-FPM или используйте скрипт сброса кэша.&lt;/li&gt;
&lt;li&gt;Если оставляете validate_timestamps=1, задайте opcache.revalidate_freq=2 (проверка не чаще чем раз в 2 секунды), чтобы не дергать диск на каждый запрос.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Для Bitrix:&lt;/strong&gt; OPcache ускоряет и публичную часть, и админку, снижает CPU; на проде он обязателен.&lt;/p&gt;




&lt;ol&gt;
&lt;li&gt;intl — обязательно для Bitrix&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Расширение &lt;a href="https://www.php.net/manual/en/book.intl.php" rel="noopener noreferrer"&gt;intl&lt;/a&gt; (Internationalization) нужно для работы с локалью, форматированием дат, чисел и строк в Unicode. Bitrix активно использует его в ядре и при работе с мультиязычностью и датами.&lt;/p&gt;

&lt;p&gt;Проверка:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
php &lt;span class="nt"&gt;-m&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;intl

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Установка (Debian/Ubuntu):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
apt &lt;span class="nb"&gt;install &lt;/span&gt;php8.2-intl

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Без intl возможны ошибки при выводе дат, сортировке строк с кириллицей и в модулях, зависящих от ICU.&lt;/p&gt;




&lt;ol&gt;
&lt;li&gt;mbstring — обязательно&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Многобайтовые строки, русский язык — без mbstring никуда.&lt;/p&gt;




&lt;ol&gt;
&lt;li&gt;mysqli / pdo_mysql&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Bitrix работает с MySQL/MariaDB через mysqli или PDO. Обычно одно из расширений уже включено в сборке PHP.&lt;/p&gt;

&lt;p&gt;Проверка:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
php &lt;span class="nt"&gt;-m&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'mysqli|pdo\_mysql'&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Если оба отключены — установите пакет, например: apt install php8.2-mysql.&lt;/p&gt;




&lt;ol&gt;
&lt;li&gt;redis — ускорение кэша Bitrix&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Bitrix поддерживает Redis в качестве managed cache (вместо файлового или memcached). Настройка описана в &lt;a href="https://dev.1c-bitrix.ru/learning/course/index.php?COURSE_ID=43&amp;amp;CHAPTER_ID=02795&amp;amp;LESSON_ID=14034" rel="noopener noreferrer"&gt;документации Bitrix&lt;/a&gt; (подключения к Redis, Memcache).&lt;/p&gt;

&lt;p&gt;Установка сервера и PHP-расширения:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
apt &lt;span class="nb"&gt;install &lt;/span&gt;redis-server

apt &lt;span class="nb"&gt;install &lt;/span&gt;php8.2-redis

systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;redis-server

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;В .settings.php (или в старом варианте в dbconn.php) задаётся тип кэша и параметры подключения:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;
&lt;span class="s1"&gt;'cache'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;

&lt;span class="s1"&gt;'value'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;

&lt;span class="s1"&gt;'type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'redis'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="s1"&gt;'redis'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;

&lt;span class="s1"&gt;'host'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'127.0.0.1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="s1"&gt;'port'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'6379'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="p"&gt;],&lt;/span&gt;

&lt;span class="p"&gt;],&lt;/span&gt;

&lt;span class="p"&gt;],&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;После смены кэша в настройках ядра Bitrix нужно сбросить кэш (Настройки → Производительность → Очистить кэш). На нагруженных проектах Redis даёт заметное ускорение по сравнению с файловым кэшем.&lt;/p&gt;




&lt;ol&gt;
&lt;li&gt;memcached — альтернатива Redis&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Работает, но Redis сейчас предпочтительнее.&lt;/p&gt;




&lt;ol&gt;
&lt;li&gt;curl — обязателен&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;API, CRM, 1С, внешние сервисы.&lt;/p&gt;

&lt;p&gt;Без curl Bitrix-интеграции страдают.&lt;/p&gt;




&lt;ol&gt;
&lt;li&gt;zip&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Bitrix обновления, архивы, маркетплейс.&lt;/p&gt;




&lt;ol&gt;
&lt;li&gt;gd / imagick&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Обработка изображений.&lt;/p&gt;

&lt;p&gt;Если проект с каталогом — imagick лучше.&lt;/p&gt;




&lt;p&gt;Что лучше отключить&lt;/p&gt;

&lt;p&gt;На продакшене отключают отладочные и избыточные опции.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;В php.ini (или в конфиге пула FPM):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;
&lt;span class="c"&gt;; Отладка — только в dev, на проде никогда
&lt;/span&gt;
&lt;span class="py"&gt;xdebug.mode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;off&lt;/span&gt;

&lt;span class="c"&gt;; Не выводить ошибки в ответ клиенту
&lt;/span&gt;
&lt;span class="err"&gt;display\&lt;/span&gt;&lt;span class="py"&gt;_errors&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;Off&lt;/span&gt;

&lt;span class="err"&gt;display\_startup\&lt;/span&gt;&lt;span class="py"&gt;_errors&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;Off&lt;/span&gt;

&lt;span class="c"&gt;; Логировать ошибки в файл
&lt;/span&gt;
&lt;span class="err"&gt;log\&lt;/span&gt;&lt;span class="py"&gt;_errors&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;On&lt;/span&gt;

&lt;span class="err"&gt;error\&lt;/span&gt;&lt;span class="py"&gt;_log&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/var/log/php-fpm/error.log&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Xdebug&lt;/strong&gt; на проде отключают: он сильно замедляет выполнение и может раскрывать внутреннюю информацию.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;display_errors = On&lt;/strong&gt; на проде недопустим: пользователь не должен видеть стек вызовов и пути к файлам.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;allow_url_fopen&lt;/strong&gt; — включать только если приложению нужен доступ к внешним URL через fopen; при использовании curl часто оставляют Off и ограничивают поверхность атак.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Проверка отключённых функций (часто хостеры режут shell_exec, exec и т.п.):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
php &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;"var&lt;/span&gt;&lt;span class="se"&gt;\_&lt;/span&gt;&lt;span class="s2"&gt;dump(ini&lt;/span&gt;&lt;span class="se"&gt;\_&lt;/span&gt;&lt;span class="s2"&gt;get('disable&lt;/span&gt;&lt;span class="se"&gt;\_&lt;/span&gt;&lt;span class="s2"&gt;functions'));"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Продакшен — это минимум включённого и максимум контроля над тем, что реально используется.&lt;/p&gt;




&lt;p&gt;Связка Apache и PHP-FPM&lt;/p&gt;

&lt;p&gt;Чтобы Apache отдавал PHP-запросы в PHP-FPM, нужны модуль proxy_fcgi (и обычно proxy) и настройка проксирования. Пример для виртуального хоста (Unix-сокет):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nl"&gt;FilesMatch&lt;/span&gt;&lt;span class="sr"&gt; \.php$&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;
&lt;span class="nc"&gt;SetHandler&lt;/span&gt; "proxy:unix:/run/php/php8.2-fpm.sock|fcgi://localhost"

&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nl"&gt;FilesMatch&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Или через TCP (если FPM слушает порт):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nl"&gt;FilesMatch&lt;/span&gt;&lt;span class="sr"&gt; \.php$&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;
&lt;span class="nc"&gt;SetHandler&lt;/span&gt; "proxy:fcgi://127.0.0.1:9000"

&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nl"&gt;FilesMatch&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Путь к сокету смотрите в конфиге пула FPM (например, в /etc/php/8.2/fpm/pool.d/&lt;a href="http://www.conf:" rel="noopener noreferrer"&gt;www.conf:&lt;/a&gt; listen = /run/php/php8.2-fpm.sock). Подробнее — в &lt;a href="https://httpd.apache.org/docs/2.4/howto/php_fpm.html" rel="noopener noreferrer"&gt;документации Apache&lt;/a&gt; (PHP-FPM) и в описании &lt;a href="https://httpd.apache.org/docs/2.4/mod/mod_proxy_fcgi.html" rel="noopener noreferrer"&gt;mod_proxy_fcgi&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Оптимальные настройки PHP-FPM для Bitrix&lt;/p&gt;

&lt;p&gt;Параметры пула задаются в /etc/php/8.2/fpm/pool.d/&lt;a href="http://www.conf" rel="noopener noreferrer"&gt;www.conf&lt;/a&gt; (или отдельном файле пула). Рекомендуемый режим — dynamic: число воркеров меняется в заданных пределах. Документация: &lt;a href="https://www.php.net/manual/en/install.fpm.configuration.php" rel="noopener noreferrer"&gt;php.net FPM configuration&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Пример:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;
&lt;span class="py"&gt;pm&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;dynamic&lt;/span&gt;

&lt;span class="err"&gt;pm.max\&lt;/span&gt;&lt;span class="py"&gt;_children&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;30&lt;/span&gt;

&lt;span class="err"&gt;pm.start\&lt;/span&gt;&lt;span class="py"&gt;_servers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;5&lt;/span&gt;

&lt;span class="err"&gt;pm.min\_spare\&lt;/span&gt;&lt;span class="py"&gt;_servers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;5&lt;/span&gt;

&lt;span class="err"&gt;pm.max\_spare\&lt;/span&gt;&lt;span class="py"&gt;_servers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;10&lt;/span&gt;

&lt;span class="err"&gt;pm.max\&lt;/span&gt;&lt;span class="py"&gt;_requests&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;500&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ориентир по памяти: если один PHP-процесс (Bitrix под нагрузкой) съедает около 100 МБ, то max_children = 30 — это до ~3 ГБ только на FPM. Учитывайте также Apache, MySQL, Redis и запас под пики — считайте под доступную RAM.&lt;/p&gt;




&lt;p&gt;Типовая схема продакшена&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Apache (mpm_event)&lt;/li&gt;
&lt;li&gt;mod_rewrite&lt;/li&gt;
&lt;li&gt;mod_ssl&lt;/li&gt;
&lt;li&gt;PHP-FPM&lt;/li&gt;
&lt;li&gt;OPcache&lt;/li&gt;
&lt;li&gt;Redis&lt;/li&gt;
&lt;li&gt;Правильные лимиты FPM&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;И вот это уже прод, а не «работает на локалке».&lt;/p&gt;




&lt;p&gt;Частые проблемы&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;502 Bad Gateway&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Apache не получает ответ от PHP-FPM. Возможные причины: FPM не запущен, закончились воркеры (max_children), таймаут при тяжёлом скрипте, неверный путь к сокету.&lt;/p&gt;

&lt;p&gt;Проверка статуса и сокета:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
systemctl status php8.2-fpm

&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /run/php/php8.2-fpm.sock

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Проверка логов:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/apache2/error.log

&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/php8.2-fpm.log

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Если 502 появляется под нагрузкой — увеличьте pm.max_children и убедитесь, что памяти хватает. При долгих запросах проверьте request_terminate_timeout в пуле FPM и таймауты в Apache (например, ProxyTimeout).&lt;/p&gt;




&lt;ol&gt;
&lt;li&gt;Медленная админка Bitrix&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Типичные причины:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Нет OPcache&lt;/strong&gt; — включите и перезапустите FPM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Нет Redis&lt;/strong&gt; — переведите managed cache на Redis в настройках ядра.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;prefork вместо event&lt;/strong&gt; — переключитесь на MPM event.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Маленький memory_limit&lt;/strong&gt; — для тяжёлых страниц админки часто ставят 256M–512M.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Файловый кэш на медленном диске&lt;/strong&gt; — замените на Redis.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Проверка OPcache в работе:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
php &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;"print&lt;/span&gt;&lt;span class="se"&gt;\_&lt;/span&gt;&lt;span class="s2"&gt;r(opcache&lt;/span&gt;&lt;span class="se"&gt;\_&lt;/span&gt;&lt;span class="s2"&gt;get&lt;/span&gt;&lt;span class="se"&gt;\_&lt;/span&gt;&lt;span class="s2"&gt;status(false));"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;ol&gt;
&lt;li&gt;Обновления Bitrix падают&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;При обновлении ядра или модулей проверьте:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Расширение zip&lt;/strong&gt; — php -m | grep zip.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Расширение intl&lt;/strong&gt; — обязательно для Bitrix.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Права на файлы&lt;/strong&gt; — владелец и группа должны совпадать с пользователем веб-сервера; каталоги с записью для обновления.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;disable_functions&lt;/strong&gt; — в списке не должно быть exec, shell_exec, proc_open и т.п., если обновление их использует (официальная документация Bitrix уточняет требования к окружению).&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Сниппеты по теме&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Apache mod_rewrite: front controller для Bitrix и Laravel&lt;/li&gt;
&lt;li&gt;Apache: привязка PHP к PHP-FPM через SetHandler&lt;/li&gt;
&lt;li&gt;OPcache: настройка для продакшена (php.ini)&lt;/li&gt;
&lt;li&gt;Bitrix: настройка Redis для managed cache (.settings.php)&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Полезные ссылки по документации&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://httpd.apache.org/docs/2.4/mod/mod_rewrite.html" rel="noopener noreferrer"&gt;Apache mod_rewrite&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://httpd.apache.org/docs/2.4/mod/mod_ssl.html" rel="noopener noreferrer"&gt;Apache mod_ssl&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://httpd.apache.org/docs/2.4/mod/mod_deflate.html" rel="noopener noreferrer"&gt;Apache mod_deflate&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://httpd.apache.org/docs/2.4/mod/mod_expires.html" rel="noopener noreferrer"&gt;Apache mod_expires&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://httpd.apache.org/docs/2.4/howto/php_fpm.html" rel="noopener noreferrer"&gt;Apache + PHP-FPM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.php.net/manual/en/book.opcache.php" rel="noopener noreferrer"&gt;PHP OPcache&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.php.net/manual/en/install.fpm.configuration.php" rel="noopener noreferrer"&gt;PHP-FPM configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.1c-bitrix.ru/learning/course/index.php?COURSE_ID=43&amp;amp;CHAPTER_ID=02795&amp;amp;LESSON_ID=14034" rel="noopener noreferrer"&gt;Bitrix: Redis, Memcache&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Связанные статьи&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Backend-автоматизация:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://dev.to/_vproger_/backend-avtomatizatsiia-biez-boli-cron-aghienty-i-inzhieniernyi-podkhod-1o87"&gt;https://viku-lov.ru/blog/backend-cron-bitrix-agents-automation&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CI/CD для PHP и Bitrix:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://dev.to/_vproger_/cicd-dlia-php-bitrix-i-laravel-github-actions-na-praktikie-biez-maghii-i-boli-1heh"&gt;https://viku-lov.ru/blog/cicd-php-bitrix-laravel-github-actions&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WordPress и OPcache (актуально и для Bitrix):&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://dev.to/_vproger_/uskorieniie-wordpress-v-2026-core-web-vitals-biez-maghii-siervier-kesh-miedia-2om1"&gt;https://viku-lov.ru/blog/wordpress-performance-2026-core-web-vitals-lcp-inp-cls-nginx-phpfpm-opcache-caching&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Итог&lt;/p&gt;

&lt;p&gt;Если коротко:&lt;/p&gt;

&lt;p&gt;Apache:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;mpm_event&lt;/li&gt;
&lt;li&gt;mod_rewrite&lt;/li&gt;
&lt;li&gt;mod_ssl&lt;/li&gt;
&lt;li&gt;mod_headers&lt;/li&gt;
&lt;li&gt;mod_expires&lt;/li&gt;
&lt;li&gt;mod_deflate / brotli&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;PHP:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;opcache&lt;/li&gt;
&lt;li&gt;intl&lt;/li&gt;
&lt;li&gt;mbstring&lt;/li&gt;
&lt;li&gt;mysqli&lt;/li&gt;
&lt;li&gt;curl&lt;/li&gt;
&lt;li&gt;redis&lt;/li&gt;
&lt;li&gt;zip&lt;/li&gt;
&lt;li&gt;gd / imagick&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Остальное — по ситуации.&lt;/p&gt;

&lt;p&gt;Если сервер нагружен — сначала проверьте:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;OPcache&lt;/li&gt;
&lt;li&gt;FPM max_children&lt;/li&gt;
&lt;li&gt;Redis&lt;/li&gt;
&lt;li&gt;MPM event&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;И только потом имеет смысл браться за «оптимизацию кода».&lt;/p&gt;

&lt;p&gt;&lt;a href="https://viku-lov.ru/blog/apache-httpd-php-fpm-modules-bitrix-production" rel="noopener noreferrer"&gt;Read more on viku-lov.ru&lt;/a&gt;&lt;/p&gt;

</description>
      <category>apache</category>
      <category>phpfpm</category>
      <category>bitrix</category>
      <category>backend</category>
    </item>
  </channel>
</rss>
