Блог

  • Внутреннее устройство PHP: глубокий разбор структуры zval

    Zval (Zend value) — фундаментальная структура данных в ядре PHP, ответственная за хранение и обработку всех переменных. В этой статье мы детально разберем её эволюцию и современную реализацию.

    Историческая эволюция

    Версия PHPРазмер zvalКлючевые изменения
    PHP 5.624 байтаУниверсальный подсчет ссылок, избыточность
    PHP 7.016 байтОптимизация хранения скаляров, новые флаги
    PHP 8.216 байтДополнительные оптимизации JIT

    Сравнение производительности

    // Тест создания 1 млн zval-ов (в нс)
    PHP 5.6: 2400 ns
    PHP 8.2: 800 ns (ускорение 3x)

    Структура zval в PHP 8

    struct _zval_struct {
        zend_value value;    // 8 байт (union)
        union {
            struct {
                zend_uchar type;        // Тип данных
                zend_uchar type_flags;  // Поведенческие флаги
                zend_uchar const_flags; // Константность
                zend_uchar reserved;    // Выравнивание
            } v;
            uint32_t type_info;         // Альтернативное представление
        } u1;                          // 4 байта
        union u2 {                     // 4 байта (служебные данные)
            uint32_t next;
            // ... другие поля
        };
    }; // Всего 16 байт

    Типы данных и их хранение

    • Скаляры (int, float, bool) — хранятся напрямую в zend_value
    • Строки — указатель на zend_string (отдельная структура в heap)
    • Массивы — указатель на HashTable
    • Объекты — указатель на zend_object

    Ключевые оптимизации

    1. Отказ от refcount для скаляров

    В PHP 7+ целые числа и булевы значения больше не используют подсчет ссылок, что устраняет накладные расходы на их копирование.

    2. Copy-On-Write для сложных структур

    $a = [1,2,3]; // Создается HashTable (refcount=1)
    $b = $a;      // Только увеличивается refcount
    $b[] = 4;     // Реальное копирование при изменении

    3. Встроенное кеширование хешей

    Строки хранят предвычисленные хеши, что ускоряет операции сравнения и поиска в хеш-таблицах.

    Практическое применение

    Для разработчиков расширений

    // Создание zval в расширении
    zval my_zval;
    ZVAL_LONG(&my_zval, 42);
    
    // Доступ к значению
    if (Z_TYPE(my_zval) == IS_LONG) {
        zend_long value = Z_LVAL(my_zval);
    }

    Оптимизации для веб-приложений

    • Используйте строгие типы (strict_types=1)
    • Избегайте избыточного копирования массивов
    • Освобождайте память unset() для больших структур

    Заключение

    • Zval в PHP 8 — высокооптимизированная структура
    • Понимание её устройства помогает писать эффективный код
    • Оптимизации PHP 7+ дают до 3x прирост производительности

  • Как создать уникальные индексы в Doctrine и MySQL: Полное руководство

    В этой статье мы разберем, как создавать уникальные индексы в Doctrine (для Symfony) и MySQL. Вы узнаете:

    • Как определять уникальные поля через Doctrine ORM
    • Особенности работы UNIQUE-индексов с NULL-значениями
    • Как создавать составные уникальные индексы
    • Различия между MySQL и другими СУБД

    1. Создание уникальных индексов в Doctrine

    1.1. Через атрибуты (PHP 8+)

    use Doctrine\ORM\Mapping as ORM;
    
    #[ORM\Entity]
    #[ORM\Table(name: 'users')]
    #[ORM\UniqueConstraint(name: 'unique_email', columns: ['email'])]
    class User
    {
        #[ORM\Id]
        #[ORM\GeneratedValue]
        #[ORM\Column]
        private ?int $id = null;
    
        #[ORM\Column(type: 'string', length: 255, unique: true)]
        private string $email;
    }

    1.2. Через YAML-конфигурацию

    App\Entity\User:
      type: entity
      table: users
      uniqueConstraints:
        unique_email:
          columns: [email]
      fields:
        email:
          type: string
          unique: true

    1.3. Составные уникальные индексы

    #[ORM\Entity]
    #[ORM\Table(uniqueConstraints: [
        new ORM\UniqueConstraint(
            name: 'unique_user_product', 
            columns: ['user_id', 'product_id']
        )
    ])]
    class CartItem
    {
        #[ORM\ManyToOne(targetEntity: User::class)]
        private User $user;
    
        #[ORM\ManyToOne(targetEntity: Product::class)]
        private Product $product;
    }

    2. Особенности работы с NULL в MySQL

    2.1. Поведение NULL в UNIQUE-индексах

    CREATE TABLE users (
        email VARCHAR(255) UNIQUE
    );
    
    INSERT INTO users (email) VALUES (NULL), (NULL); -- Разрешено в MySQL

    2.2. Как запретить дубликаты NULL

    Вариант 1: Использовать NOT NULL

    CREATE TABLE users (
        email VARCHAR(255) NOT NULL DEFAULT '',
        UNIQUE (email)
    );

    Вариант 2: Триггер для проверки

    DELIMITER //
    CREATE TRIGGER prevent_null_duplicates
    BEFORE INSERT ON users
    FOR EACH ROW
    BEGIN
        IF NEW.email IS NULL AND EXISTS (
            SELECT 1 FROM users WHERE email IS NULL
        ) THEN
            SIGNAL SQLSTATE '45000' 
            SET MESSAGE_TEXT = 'Duplicate NULL values not allowed';
        END IF;
    END//
    DELIMITER ;

    Минус триггера, дополнительная нагрузка на БД и время выполнения запроса.

    3. Различия между СУБД

    СУБДПоведение NULL в UNIQUE
    MySQLРазрешает несколько NULL
    PostgreSQLРазрешает только один NULL

    4. Проверка индексов

    В Doctrine:

    php bin/console doctrine:schema:validate

    В MySQL:

    SHOW INDEX FROM users WHERE Non_unique = 0;

    Заключение

    • Используйте unique: true или UniqueConstraint в Doctrine
    • Помните о различиях в обработке NULL разными СУБД
    • Для строгой уникальности заменяйте NULL на пустые строки
    • Составные индексы работают по-разному в MySQL 8.0+

    Совет: Всегда проверяйте поведение UNIQUE-индексов в вашей версии СУБД перед развертыванием в production.

  • Реактивность в Vue 3: watch, watchEffect и продвинутые техники

    Vue 3 представляет мощную систему реактивности, которая является фундаментом для создания современных веб-приложений. В этом руководстве мы глубоко погрузимся в механизмы отслеживания изменений, рассмотрим все аспекты работы watch и watchEffect, а также изучим продвинутые паттерны работы с реактивностью.

    Основы реактивности в Vue 3

    Vue 3 полностью переработал систему реактивности, используя JavaScript Proxy вместо Object.defineProperty. Это обеспечивает:

    • Поддержку работы с массивами и коллекциями
    • Более эффективное отслеживание изменений
    • Возможность создания «сырых» (raw) объектов без реактивности
    • Лучшую интеграцию с TypeScript

    Как работает реактивность

    import { reactive, effect } from 'vue'
    
    const state = reactive({
      count: 0
    })
    
    // Аналог watchEffect в системе реактивности
    effect(() => {
      console.log('Count:', state.count)
    })

    watch vs watchEffect: полное сравнение

    watchEffect

    • Автоматическое отслеживание зависимостей
    • Немедленный запуск при создании
    • Не предоставляет старые значения
    • Идеален для побочных эффектов
    const count = ref(0)
    
    watchEffect(() => {
      console.log(`Count changed: ${count.value}`)
    })

    watch

    • Явное указание источников
    • Ленивое выполнение (по умолчанию)
    • Предоставляет старые и новые значения
    • Подходит для сравнения состояний
    watch(count, (newVal, oldVal) => {
      console.log(`Count changed from ${oldVal} to ${newVal}`)
    })

    Продвинутые техники работы с реактивностью

    1. Контроль зависимостей

    const condition = ref(false)
    const a = ref(1)
    const b = ref(2)
    
    watchEffect(() => {
      // Только condition будет зависимостью
      if (condition.value) {
        console.log(a.value + b.value) // a и b не станут зависимостями
      }
    })

    2. Глубокое наблюдение с кастомным сравнением

    watch(
      () => ({ ...complexObject }),
      (newVal, oldVal) => {
        // Логика сравнения
      },
      {
        deep: true,
        equals: (a, b) => 
          a.id === b.id && a.items.length === b.items.length
      }
    )

    3. Реактивные цепочки и оптимизация

    const searchQuery = ref('')
    const filters = reactive({ 
      category: '', 
      sort: 'asc',
      // 10K элементов
      items: hugeArray 
    })
    
    // Оптимизированный watch
    watch(
      () => ({
        query: searchQuery.value,
        category: filters.category,
        sort: filters.sort
      }),
      ({ query, category, sort }) => {
        // Фильтрация без отслеживания hugeArray
        filterItems(query, category, sort)
      }
    )

    Практические примеры из реальных проектов

    1. Интеграция с API

    const pagination = reactive({
      page: 1,
      size: 20,
      total: 0
    })
    
    const fetchData = async () => {
      const res = await api.get('/items', {
        params: {
          page: pagination.page,
          size: pagination.size
        }
      })
      pagination.total = res.total
    }
    
    // Автоматический запрос при изменении пагинации
    watch([() => pagination.page, () => pagination.size], fetchData, {
      immediate: true
    })

    2. Управление состоянием формы

    const form = reactive({
      email: '',
      password: '',
      errors: {}
    })
    
    watch(
      () => form.email,
      (email) => {
        form.errors.email = validateEmail(email) 
          ? null 
          : 'Invalid email format'
      },
      { debounce: 300 }
    )

    Производительность и отладка

    1. Измерение времени выполнения

    watchEffect((onCleanup) => {
      const start = performance.now();
      
      // Тяжелая операция
      processLargeData();
      
      const duration = performance.now() - start;
      if (duration > 50) {
        console.warn(`Slow effect: ${duration.toFixed(2)}ms`);
      }
      
      onCleanup(() => {
        // Очистка ресурсов
      })
    })

    2. Визуализация зависимостей

    function trackDependencies(effect) {
      const deps = new Set()
      
      const reactiveEffect = watchEffect(() => {
        effect()
        console.log('Dependencies:', [...deps])
        deps.clear()
      }, {
        onTrack(e) {
          deps.add(e.target[e.key])
        }
      })
      
      return reactiveEffect
    }

    Заключение и лучшие практики

    • Используйте watch когда нужны старые значения или точный контроль
    • Выбирайте watchEffect для автоматического отслеживания зависимостей
    • Оптимизируйте глубокое наблюдение с помощью кастомных функций сравнения
    • Разделяйте сложные эффекты на несколько простых watchers
    • Всегда очищайте ресурсы в onCleanup
    • Мониторьте производительность сложных эффектов

    Эти техники помогут вам создавать высокопроизводительные приложения с четкой и предсказуемой реактивностью.

  • Методы массивов в JavaScript для поиска и проверки элементов в массиве: some, every, includes, find, findIndex.

    В JavaScript у массивов есть несколько методов для проверки элементов. В этой статье разберём:

    • some() — проверяет, удовлетворяет ли хотя бы один элемент условию.
    • Похожие методы: every(), includes(), find(), findIndex().
    • Примеры и сравнение с аналогами.

    1. Array.some(): Хотя бы один элемент проходит проверку

    Синтаксис

    arr.some(callback(element, index, array));

    — Возвращает true, если хотя бы один элемент соответствует условию.
    — Иначе — false.

    Пример

    const numbers = [1, 2, 3, 4, 5];
    
    // Есть ли хотя бы одно чётное число?
    const hasEven = numbers.some(num => num % 2 === 0);
    console.log(hasEven); // true (2 и 4 подходят)

    Когда использовать?

    Проверка, что в массиве есть хотя бы один подходящий элемент.


    2. Array.every(): Все элементы проходят проверку

    Синтаксис

    arr.every(callback(element, index, array));

    — Возвращает true, если все элементы удовлетворяют условию.

    Пример

    const ages = [18, 22, 25, 30];
    
    // Все ли совершеннолетние?
    const allAdults = ages.every(age => age >= 18);
    console.log(allAdults); // true

    Сравнение some() и every()

    МетодВозвращает true, если…Аналог в логике
    some()Хотя бы один элемент подходит|| (ИЛИ)
    every()Все элементы подходят&& (И)

    3. Array.includes(): Проверка наличия конкретного значения

    Синтаксис

    arr.includes(value, fromIndex);

    — Проверяет, есть ли конкретное значение в массиве.

    Пример

    const fruits = ['apple', 'banana', 'orange'];
    
    console.log(fruits.includes('banana')); // true
    console.log(fruits.includes('grape'));  // false

    includes() vs some()

    includes() — ищет конкретное значение.
    some() — проверяет условие (например, через функцию).

    // Эквивалентные проверки:
    const numbers = [1, 2, 3];
    
    // Через includes()
    numbers.includes(2); // true
    
    // Через some()
    numbers.some(num => num === 2); // true

    4. Array.find() и findIndex(): Поиск первого подходящего элемента

    find()

    Возвращает первый элемент, удовлетворяющий условию:

    const users = [
      { id: 1, name: 'Alice' },
      { id: 2, name: 'Bob' },
    ];
    
    const bob = users.find(user => user.name === 'Bob');
    console.log(bob); // { id: 2, name: 'Bob' }

    findIndex()

    Возвращает индекс первого подходящего элемента (или -1):

    const index = users.findIndex(user => user.name === 'Bob');
    console.log(index); // 1

    Сравнение с some()

    МетодВозвращаетПодходит для…
    some()true/falseПроверка наличия
    find()Элемент или undefinedПолучение объекта
    findIndex()Индекс или -1Удаление/замена элемента

    5. Итог: Какой метод выбрать?

    ЗадачаМетод
    Есть ли хотя бы один подходящий?some()
    Все элементы подходят?every()
    Есть ли конкретное значение?includes()
    Найти первый подходящий элементfind()
    Найти индекс элементаfindIndex()

    Примеры использования

    ❶ Проверка прав доступа

    const permissions = ['read', 'write', 'delete'];
    
    // Есть ли право на запись?
    const canWrite = permissions.some(perm => perm === 'write');

    ❷ Валидация формы

    const inputs = ['', 'test@example.com', '123'];
    
    // Все ли поля заполнены?
    const isValid = inputs.every(input => input.trim() !== '');

    ❸ Поиск в массиве объектов

    const products = [
      { id: 1, name: 'Laptop', inStock: true },
      { id: 2, name: 'Phone', inStock: false },
    ];
    
    // Есть ли хотя бы один товар в наличии?
    const hasStock = products.some(product => product.inStock);

    Вывод

    some() — лучший выбор для проверки хотя бы одного элемента.
    every() — если нужно убедиться, что все элементы подходят.
    includes() — для простой проверки значений.
    find()/findIndex() — если нужен сам элемент или его индекс.

    Используйте эти методы, чтобы писать чистый и эффективный код! 🚀

  • Как работать с GROUP BY и SUM в Doctrine

    Doctrine ORM предоставляет мощные инструменты для работы с агрегатными функциями SQL. В этом руководстве мы разберем использование GROUP BY и SUM в Symfony-проектах.

    1. Базовые примеры

    1.1. Простая группировка с суммированием

    // ProductRepository.php
    public function getCategoryStats()
    {
        return $this->createQueryBuilder('p')
            ->select([
                'p.category',
                'SUM(p.price) as total_price',
                'COUNT(p.id) as product_count'
            ])
            ->groupBy('p.category')
            ->getQuery()
            ->getResult();
    }

    1.2. Группировка по дате

    public function getMonthlySales()
    {
        return $this->createQueryBuilder('o')
            ->select([
                "DATE_FORMAT(o.createdAt, '%Y-%m') as month",
                'SUM(o.total) as sales'
            ])
            ->groupBy('month')
            ->getQuery()
            ->getResult();
    }

    2. Продвинутые сценарии

    2.1. Фильтрация с HAVING

    public function getHighValueOrders($minAmount)
    {
        return $this->createQueryBuilder('o')
            ->select([
                'c.name',
                'SUM(o.total) as total'
            ])
            ->join('o.customer', 'c')
            ->groupBy('c.id')
            ->having('total > :minAmount')
            ->setParameter('minAmount', $minAmount)
            ->getQuery()
            ->getResult();
    }

    2.2. Группировка с JOIN связанных сущностей

    public function getSalesByCategoryAndUser()
    {
        return $this->createQueryBuilder('o')
            ->select([
                'p.category',
                'u.name',
                'SUM(o.total) as total'
            ])
            ->join('o.product', 'p')
            ->join('o.user', 'u')
            ->groupBy('p.category, u.id')
            ->getQuery()
            ->getResult();
    }

    3. Оптимизация запросов

    • Добавляйте индексы для полей группировки
    • Используйте кеширование для сложных отчетов
    • Ограничивайте выборку при работе с большими данными

    4. Частые проблемы

    ПроблемаРешение
    Ошибка «Non-selected field in GROUP BY»Включите все неагрегированные поля в GROUP BY
    Медленные запросыДобавьте индексы и используйте LIMIT

    Заключение

    GROUP BY и SUM в Doctrine — мощные инструменты для аналитики. Ключевые правила:

    • Используйте индексы для полей группировки
    • Для сложных отчетов применяйте нативные SQL-запросы
    • Тестируйте запросы на реальных данных

  • Интеграция i18n с Pinia во Vue3: полное руководство

    В этой статье мы разберем, как эффективно использовать систему интернационализации (i18n) вместе с хранилищами Pinia во Vue.js приложениях.

    1. Установка и базовая настройка

    npm install pinia vue-i18n
    # или
    yarn add pinia vue-i18n

    2. Создание локалей

    Создайте JSON-файлы с переводами:

    // locales/ru.json
    {
      "cart": {
        "title": "Корзина",
        "empty": "Ваша корзина пуста"
      }
    }

    3. Инициализация i18n

    // i18n.js
    import { createI18n } from 'vue-i18n';
    import ru from './locales/ru.json';
    
    export default createI18n({
      locale: 'ru',
      fallbackLocale: 'en',
      messages: { ru }
    });

    4. Создание хранилища с i18n

    // stores/cartStore.js
    import { defineStore } from 'pinia';
    import { useI18n } from 'vue-i18n';
    
    export const useCartStore = defineStore('cart', {
      state: () => ({ items: [] }),
      getters: {
        cartTitle: () => {
          const { t } = useI18n();
          return t('cart.title');
        }
      },
      actions: {
        showEmptyMessage() {
          const { t } = useI18n();
          alert(t('cart.empty'));
        }
      }
    });

    5. Использование в компонентах

    <script setup>
    import { useCartStore } from '@/stores/cartStore';
    const cartStore = useCartStore();
    </script>
    
    <template>
      <h1>{{ cartStore.cartTitle }}</h1>
      <button @click="cartStore.showEmptyMessage">
        Проверить корзину
      </button>
    </template>

    6. Переключение языков

    // stores/localeStore.js
    export const useLocaleStore = defineStore('locale', {
      actions: {
        setLocale(lang) {
          const { locale } = useI18n();
          locale.value = lang;
        }
      }
    });

    7. Частые проблемы и решения

    • Ошибка «useI18n called outside setup()» — убедитесь, что используете хук только в setup()
    • Нет реактивности — оберните переводы в computed()

    8. Альтернативные подходы

    • Использование provide/inject для i18n
    • Создание отдельного хранилища для локализации
    • Использование composable-функций
  • Основные настройки PHP-FPM для веб-сервера

    Правильная конфигурация PHP-FPM критически важна для безопасности и стабильности работы веб-приложений. Разберём ключевые параметры из файла конфигурации пула (обычно /etc/php/8.x/fpm/pool.d/www.conf).

    1. Пользователь и группа (user/group)

    user = www-data
    group = www-data
    • Назначение: Определяет, от какого пользователя будут выполняться PHP-скрипты
    • Рекомендации:
      • Всегда используйте отдельного пользователя (не root!)
      • www-data — стандартный пользователь для веб-серверов в Ubuntu/Debian
      • Для изоляции разных сайтов создавайте отдельных пользователей
    • Безопасность: Ограничивает права PHP-скриптов в системе

    2. Группа сокета (listen.group)

    listen.group = www-data
    • Назначение: Указывает группу, которой доступен Unix-сокет PHP-FPM
    • Важно: Веб-сервер Nginx должен быть в этой же группе
    • Проверка:
      ps aux | grep nginx | grep -v grep
      groups www-data

    3. Права доступа к сокету (listen.mode)

    listen.mode = 0660
    ПраваВладелецГруппаОстальные
    0660rw-rw-
    • Безопасность: Запрещает доступ всем, кроме владельца и группы
    • Альтернативы:
      • 0666 — небезопасно (доступ всем)
      • 0600 — только владельцу (могут быть проблемы с веб-сервером)

    4. Дополнительные важные настройки

    ПараметрРекомендуемое значениеОписание
    pmondemandРежим управления процессами (для серверов с переменной нагрузкой)
    pm.max_children50Максимальное число процессов (зависит от памяти сервера)
    request_terminate_timeout30sМаксимальное время выполнения скрипта
    security.limit_extensions.phpОграничивает выполнение только PHP-файлов

    5. Проверка и применение настроек

    # Проверка синтаксиса
    sudo php-fpm8.x -t
    
    # Перезагрузка PHP-FPM
    sudo systemctl restart php8.x-fpm
    
    # Проверка прав сокета
    ls -la /run/php/php8.x-fpm.sock

    Вывод

    • Используйте отдельного пользователя/группу для PHP-FPM
    • Ограничивайте права доступа к сокету (0660 — оптимально)
    • Регулярно проверяйте логи PHP-FPM на ошибки
    • Настройки зависят от нагрузки и специфики приложения

    Эти настройки обеспечат баланс между безопасностью и производительностью вашего веб-сервера.

  • Как добавить пользователя с правами www-data на Ubuntu 22.04 для веб-разработки

    При настройке веб-сервера часто требуется создать пользователя с ограниченными правами, но с доступом к файлам веб-приложений. В этом руководстве я покажу, как правильно добавить пользователя в группу www-data на Ubuntu 22.04.

    Зачем это нужно?

    • Безопасный доступ к файлам веб-сервера без root-прав
    • Корректная работа PHP-скриптов и WordPress
    • Возможность совместной работы над проектом
    • Правильные права для загрузки файлов через веб-интерфейс

    Шаг 1: Создаем нового пользователя

    sudo adduser devuser

    Замените «devuser» на имя вашего пользователя. Система запросит задать пароль и дополнительную информацию (можно пропустить).

    Шаг 2: Добавляем пользователя в группу www-data

    sudo usermod -aG www-data devuser
    sudo groups devuser | grep www-data

    Шаг 3: Настраиваем права на веб-папки

    sudo chown -R www-data:www-data /var/www/html
    sudo chmod -R 775 /var/www/html

    Эти команды:

    • Меняют владельца на www-data
    • Дают группе www-data права на запись
    • Сохраняют права на выполнение для всех

    Шаг 4: Настройка PHP-FPM (если используется)

    sudo nano /etc/php/8.1/fpm/pool.d/www.conf

    Добавьте или измените строки:

    user = www-data
    group = www-data
    listen.group = www-data
    listen.mode = 0660

    Перезапустите PHP-FPM:

    sudo systemctl restart php8.1-fpm

    Шаг 5: Проверка настроек

    sudo -u devuser -g www-data touch /var/www/html/test.txt

    Если файл создался — настройки верны.

    SSH доступ

    sudo rsync --archive --chown=devuser:www-data ~/.ssh /home/devuser/
    sudo chmod 700 /home/devuser/.ssh
    sudo chmod 600 /home/devuser/.ssh/authorized_keys

    Важные нюансы

    • Не давайте пользователю sudo без необходимости
    • Для системных задач используйте sudo -u www-data
    • Проверяйте логи при ошибках: sudo tail -f /var/log/nginx/error.log

    Заключение

    Теперь у вас есть безопасно настроенный пользователь для работы с веб-проектами. Это особенно полезно при:

    • Разработке в команде
    • Настройке CI/CD
    • Управлении правами на production-сервере

    Если у вас есть вопросы или дополнения — оставляйте комментарии!

  • Как настроить SSL-сертификат для WordPress с помощью Certbot

    Безопасность сайта на WordPress критически важна. В этом руководстве мы настроим бесплатный SSL-сертификат от Let’s Encrypt с помощью Certbot для защиты данных пользователей и улучшения SEO.

    1. Подготовка сервера

    2. Установка Certbot

    sudo apt update
    sudo apt install certbot python3-certbot-nginx

    3. Получение SSL-сертификата

    sudo certbot --nginx -d ваш-домен.ru -d www.ваш-домен.ru

    Certbot автоматически:

    • Проверит владение доменом
    • Получит сертификат
    • Настроит веб-сервер
    • Создаст автоматическое продление

    4. Настройка WordPress

    • В файле wp-config.php добавьте:
      define('FORCE_SSL_ADMIN', true);
      define('FORCE_SSL', true);
    • В админке: Настройки → Общие → Измените URL сайта на https://

    5. Проверка и обслуживание

    # Проверка срока действия
    sudo certbot certificates
    
    # Тест автоматического продления
    sudo certbot renew --dry-run
    
    # Принудительное обновление
    sudo certbot renew --force-renewal

    6. Автоматическое продление сертификата

    • Обычно автоматическое продление настраивается, после создание сертификатов. Проверим это.
    • Запустите certbot renew --dry-run
    • Если в результате, вы видете Congratulations, all simulated renewals succeeded, значит автоматическое продление настроено.
    • Если по какой-то причине это не так, можно добавить руками запуск команды обновления через cron.
      0 12 * * * /usr/bin/certbot renew --quiet

    Заключение

    Всего за 10 минут вы:

    • Защитили передачу данных
    • Улучшили позиции в поисковиках
    • Избежали предупреждений «Небезопасный сайт»
    • Настроили автоматическое обновление сертификата
  • Как привязать домен с Nic.ru к серверу FirstVDS в 2025: полное руководство.

    Если у вас есть домен на Nic.ru и VDS-сервер на FirstVDS, но вы не знаете, как их соединить — это руководство поможет вам разобраться во всех тонкостях процесса.

    Что вам понадобится

    • Доступ к панели управления Nic.ru
    • Доступ к серверу FirstVDS
    • IP-адрес вашего VDS (можно найти в панели FirstVDS)
    • Установленный веб-сервер Nginx.
    • 15-30 минут времени

    Шаг 1: Настройка DNS на Nic.ru

    Использование DNS FirstVDS

    1. Зайдите Личный кабинет в Nic.ru
    2. В активные услуги, выберите «Домены»
    3. Выберите ваш домен → «DNS-серверы» → «Изменить»
    4. Выберите «Указать DNS-серверы самостоятельно»
    5. Введите DNS FirstVDS (обычно ns1.firstvds.ru и ns2.firstvds.ru)
    6. Сохраните изменения

    Примечание: Изменения DNS могут распространяться до 24 часов.

    Шаг 2: Настройка сервера на FirstVDS

    Для пользователей панели управления

    1. Зайдите в панель управления (ISPmanager, Webmin и т.д.)
    2. Найдите раздел «Домены»
    3. Добавьте новый домен
    4. Укажите корневую директорию сайта

    Для ручной настройки (Nginx)

    server {
        listen 80;
        server_name ваш-домен.ru www.ваш-домен.ru;
        root /var/www/ваш-сайт;
        index index.php index.html index.htm;
        
        # другие настройки
    }

    Перезапустите сервер nginx:

    systemctl reload nginx
    # или
    service nginx reload

    Шаг 3: Настройка A-записи

    1. Зайти в DNS Manager в FirstVds. Ссылка на DNS Manager должна придти в письме, вместе с настройками VDS. Либо найти в личном кабинете.
    2. Переходим в «Управление доменами», нажимаем «Создать».
    3. Указываем свой домен и IP-адрес вашего VDS-сервера.

    Шаг 4: Дополнительные настройки

    Настройка SSL-сертификата

    1. Установите Certbot: sudo apt install certbot python3-certbot-nginx
    2. Получите сертификат: sudo certbot --nginx -d ваш-домен.ru -d www.ваш-домен.ru
    3. Настройте автоматическое обновление: sudo certbot renew --dry-run
    4. Поблее подробно про настройку SSL-сертификата читайте здесь.

    Перенаправление с www на без www

    server {
        listen 80;
        server_name www.ваш-домен.ru;
        return 301 $scheme://ваш-домен.ru$request_uri;
    }

    Шаг 4: Проверка работы

    • Проверьте DNS-записи на DNSchecker.org
    • Проверьте доступность сайта в браузере
    • Просмотрите логи ошибок: sudo tail -f /var/log/nginx/error.log

    Заключение

    Теперь ваш домен с Nic.ru должен быть успешно привязан к серверу FirstVDS. Если возникли проблемы — проверьте правильность DNS-записей и настройки веб-сервера.