Настройка хостинга на RU-Center (nic.ru)
Внимание! Данный пост был опубликован более года назад и, возможно, уже утратил свою былую актуальность. Но это не точно.

Так уж сложилось, что время от времени приходится поднимать сайты на хостинге руцентра. Как правило это небольшие сайты-визитки или проекты, для которых реализация на дедике характеризуется как “жирно будет”.

Итак, отбросим причины в сторону. Первым делом - что нам понадобится для того чтоб всё сделать “под ключ”, т.е. владельцем домена/хостинга был заказчик (как физ.лицо), а ты был лишь лицом, которое выполняет работу? От заказчика тебе потребуется:

  • Фотографии/сканы разворота паспорта и страницы с пропиской;
  • Логин/пароль от почтового ящика, который будет фигурировать при регистрации;
  • Необходимая сумма денег для оплаты требуемых услуг.

Вопрос: При регистрации домена может сразу встать вопрос - делегировать домен на руцентр или, например, сразу же на DNS-хостинге от Яндекса?

Ответ: На руцентр. Так как для делегирования на Яндекс потребуется подтвердить права на владение доменом. И проще всего это сделать с помощью проверки наличия определенного файла с кодом в корне сайта. Поэтому - смело всё делай по дефолту, потом переделегируем, если потребность такая возникнет.

Сразу скажу - если “с сайта” будут отправляться письма и при этом хостить DNS у Яндекса - возникнут проблемы с отправкой писем. DKIM настроить на Яндекс будет невозможно, а у руцентра он принципиально не настраиваемый. Поэтому считай это тонкостью использования руцентра в целом.

Итак, считаем что аккаунт у нас заведен, домен - зарегистрирован, хостинг - заказан. Не забудь а Личных данных приложить сканы/фотографии паспорта клиента для верификации личности, так правильнее будет.

Настройка почты

Переходим “Панель управления” → “Почтовый сервер”. Выбираем наш почтовый домен (должен быть создан автоматически; в противном случае создаем его с именем основного домена). Первым делом лезем в “Почтовые ящики” → “[email protected]_domain.ltd”, и добавляем синонимы к ящику вида [email protected] [email protected] [email protected] [email protected]. Таким образом все “важные” письма будут первым делом попадать именно на этот ящик, а владелец сайта сможет написать на своих визитках “клёвый” почтовый адрес [email protected]_domain.ltd :) Так же заходим в интерфейс самого почтового ящика (нажимаем на его адрес вверху страницы) и настраиваем переадресацию всех входящих писем на email-адрес заказчика.

Далее “Панель управления” → “Почтовый сервер” → “Параметры”, и в поле “Обработка нераспознанной почты” вводим [email protected]_domain.ltd. Таким образом письма, отправленные на несуществующий ящик (_например blabla@yourdomain.ltd) будут уходить на [email protected]_domain.ltd, а оттуда - нашему заказчику. Если же будут заведены дополнительные адреса (например - для сотрудников нашего заказчика) - ничего перенастраивать не придется.

Настройка СУБД

Настройку СУБД производить в соответствии с используемой системой управления контента. Рекомендую “выключать” все привилегии для пользователя, которые ему критически не важны. В идеале ему должно хватать лишь INSERT UPDATE SELECT и DELETE. Но это лишь в идеале. Ещё раз - тут смотри сам.

Больше особо интересных моментов в настройке для хостинга нет.

Настройка Веб-сервера

Вот тут самое пожалуй интересное. Первым делом мы осуществляем “преднастройку” в веб-интерфейсе панели управления. Если используешь CMS “WordPress”, то для тебя подойдут почти все настройки что идут “по умолчанию”, но не все. Для других CMS - надо смотреть более детально и отдельно.

Выключаем WP_CRON для WordPress

В WordPress Есть такая штука как WP_CRON, которая позволяет выполнять “отложенные” действия, такие как отложенная публикация постов, проверка наличия обновлений и прочие весьма полезные штуки. Но это довольно тяжелая задача, надо признать, и выполнять её при каждом посещении пользователя/администратора сайта - иметь лишнюю задержку. Что мы делаем чтоб ситуацию исправить? Мы выключаем WP_CRON в WordPress путем добавления строки:

define('DISABLE_WP_CRON', true);

В wp-config.php, и добавляем отдельную задачу в “Панель управления” → “Веб-сервер” → “Планировщик заданий” с именем, например - “wp cron” и выполняемой командой:

/usr/local/bin/wget -O - -q "http://your_domain.ltd/wp-cron.php"

Теперь пользователи не будут ощущать задержку, а все задачи крона WP будут выполняться “в фоне” с заданным интервалом (выставь “Каждые 5 минут”).

Настраиваем модули

Переходим в “Управление модулями”. Здесь нам необходимо выполнить предварительную настройку как веб-сервера (Apache, а точнее просто указать какие модули ему загружать), выбрать используемую версию php (не знаю почему, но я всё ещё пользуюсь версией 5.3), и указать необходимые параметры для php, такие как кодировка (выставляй везде UTF-8), максимальный размер загружаемого файла, и модули php (выставляй их в соответствии с требованиями сайта).

Вырубай всё откровенно лишнее и неиспользуемое. После этого перезапусти веб-сервер путем нажатия на соответствующую ссылку, и ставь свою CMS. На этом шаге у тебя должно всё работать как надо. Проверь всё - отправку писем, работу всех частей как пользовательского интерфейса, так и административной части. Всё должно работать. Только после этого мы переходим к следующей части.

Перевод сервера в ручной режим

Это необходимо для того, чтоб выполнить более тонкую настройку, недоступную из веб-интерфейса. Для перевода работы веб-сервера в ручной режим переходи в “Панель управления” → “Веб-сервер” и в графе “Режим настройки” жми на “Ручной”. После этого действия будут созданы конфиги на основе тех настроек, которые мы выполнили ранее.

Теперь цепляемся к хостингу по ssh (реквизиты для соединения указаны в “Панель управления” → “Помощь”), и все дальнейшие настройки будем выполнять только в нем.

Настройка nginx

Первым делом нам необходимо настроить:

  • Отдачу статического контента с помощью nginx
  • Включить его сжатие с помощью gzip
  • Настроить его хранение на стороне клиента (вместо того, чтоб каждый раз скачивать его с нашего сервера)

Для этого выполняем в консоли:

$ cat /etc/nginx/your_domain.ltd.site.conf

И смотрим как у нас был настроен nginx в автоматическом режиме. Он отдавал статику, но сжатие и хранение статики на стороне клиента - отсутствует. Испраляем-с :) Копируем из этого конфига все location-ы (в моем случае это были location /, location ~* ^.+\.(jpg|jpeg|gif|... и location @fallback) в конфиг ~/etc/nginx/nginx.conf, вставляя их в секцию server {...}. Сделать по образу и подобию не думаю что составит трудность.

Для включения gzip-сжатия статики необходимо в секцию http {...} добавить:

  gzip_static on;
  gzip on;
  gzip_buffers 16 8k;
  gzip_comp_level 2;
  gzip_min_length 1024;
  gzip_types text/css text/plain text/json text/x-js text/javascript text/xml application/json application/x-javascript application/xml application/xml+rss application/javascript;
  gzip_disable "msie6";
  gzip_vary on;
  gzip_http_version 1.0;

Добавить можно в принципе в любом месте перед началом секции server {...}. Теперь статика будет отдаваться значительно быстрее :)

Для того чтоб она лишний раз не загружалась с сервера, а хранилась на стороне клиента мы немного подправил location который отвечает за отдачу статики, а точнее добавим в него строку expires 30d;, чтоб получилось примерно следующее:

location ~* ^.+\.(jpg|jpeg|gif|swf|png|ico|mp3|css|zip|tgz|gz|rar|bz2|doc|xls|exe|pdf|dat|avi|ppt|txt|tar|mid|midi|wav|bmp|rtf|wmv|mpeg|mpg|mp4|m4a|spx|ogx|ogv|oga|webm|weba|ogg|tbz|js|7z)$ {
    expires 30d;
    root   /home/domain-tld/your_domain.ltd/docs;
    access_log  /var/log/your_domain.ltd.access_log  combined;
    error_page 404 = @fallback;
    log_not_found off;
    accel_htaccess_switch on;
  }

Так же приведу для сравнения полный листинг получившегося у меня конфига:

worker_processes  2;
error_log  /dev/null;
pid        /var/run/nginx.pid;
events {
    worker_connections  2048;
}
http {
  set_real_ip_from 10.1.0.0/16;      # MSK
  set_real_ip_from 10.3.0.0/16;      # MSK
  set_real_ip_from 195.208.0.0/23;   # MSK
  set_real_ip_from 212.192.194.0/24; # NSK
  set_real_ip_from 10.12.0.0/16;     # NSK
  set_real_ip_from 212.192.193.0/24; # NSK
  set_real_ip_from 10.15.0.0/16;     # AMS
  set_real_ip_from 178.210.94.0/24;  # AMS
  real_ip_header X-Real-IP;

  gzip_static on;
  gzip on;
  gzip_buffers 16 8k;
  gzip_comp_level 2;
  gzip_min_length 1024;
  gzip_types text/css text/plain text/json text/x-js text/javascript text/xml application/json application/x-javascript application/xml application/xml+rss application/javascript;
  gzip_disable "msie6";
  gzip_vary on;
  gzip_http_version 1.0;

  include       /usr/local/etc/nginx/mime.types;
  default_type  application/octet-stream;
  server_names_hash_bucket_size 128;
  access_log      off;
  sendfile        on;
  keepalive_timeout  65;

  #include /etc/nginx/virts_list;
  server {
    listen       10.3.138.12:80;
    server_name  your_domain.ltd your_domain.nichost.ru www.your_domain.ltd your_domain.nichost.ru;
    location / {
      proxy_pass         http://10.3.138.12:8080;
      proxy_redirect     http://your_domain.ltd:8080/ /;
      proxy_redirect     http://your_domain.nichost.ru:8080/ /;
      proxy_redirect     http://www.your_domain.ltd:8080/ /;
      proxy_redirect     http://www.your_domain.nichost.ru:8080/ /;
      proxy_set_header   Host             $host;
      proxy_set_header   X-Real-IP        $remote_addr;
      proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
      client_max_body_size       192m;
      client_body_buffer_size    128k;
      proxy_connect_timeout      90;
      proxy_send_timeout         900;
      proxy_read_timeout         900;
      proxy_buffer_size          64k;
      proxy_buffers              8 32k;
      proxy_busy_buffers_size    64k;
      proxy_temp_file_write_size 64k;
    }
    include /home/your_domain/etc/nginx/secure_wordpress.inc;

    # Set error pages
    set $errordocs /home/your_domain/your_domain.ltd/errordocs;
    error_page 400 /400.html; location = /400.html {root $errordocs;}
    error_page 401 /401.html; location = /401.html {root $errordocs;}
    error_page 403 /403.html; location = /403.html {root $errordocs;}
    error_page 404 /404.html; location = /404.html {root $errordocs;}
    error_page 408 /408.html; location = /408.html {root $errordocs;}
    error_page 500 /500.html; location = /500.html {root $errordocs;}
    error_page 501 /501.html; location = /501.html {root $errordocs;}
    error_page 502 /502.html; location = /502.html {root $errordocs;}
    error_page 503 /503.html; location = /503.html {root $errordocs;}
    error_page 504 /504.html; location = /504.html {root $errordocs;}

    # Static files location
    location ~*
  ^.+\.(jpg|jpeg|gif|swf|png|ico|mp3|css|zip|tgz|gz|rar|bz2|doc|xls|exe|pdf|dat|avi|ppt|txt|tar|mid|midi|wav|bmp|rtf|wmv|mpeg|mpg|mp4|m4a|spx|ogx|ogv|oga|webm|weba|ogg|tbz|js|7z)$ {
      expires 30d;
      root   /home/your_domain/your_domain.ltd/docs;
      access_log  /var/log/your_domain.ltd.access_log  combined;
      # original nic.ru line below:
      #error_page 404 = @fallback;
      log_not_found off;
      accel_htaccess_switch on;
    }
    location @fallback {
      proxy_pass http://10.3.138.12:8080;
      proxy_set_header   Host             $host;
      proxy_set_header   X-Real-IP        $remote_addr;
      proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
      client_max_body_size       192m;
      client_body_buffer_size    128k;
      proxy_connect_timeout      90;
      proxy_send_timeout         900;
      proxy_read_timeout         900;
      proxy_buffer_size          64k;
      proxy_buffers              8 32k;
      proxy_busy_buffers_size    64k;
      proxy_temp_file_write_size 64k;
    }
  }
}

Содержимое файла “~/etc/nginx/secure_wordpress.inc”:

if ($http_user_agent ~* ((perl|php|python|wpscan)|^(|-|_)$)) {return 403;}
if ($http_user_agent ~* (nmap|nikto|wikto|sf|sqlmap|bsqlbf|w3af|acunetix|havij|appscan|nic.ru|monitoring|semalt|virusdie|indy)) {return 444;}
if ($http_referer ~* (semalt.com|virusdie|mskshops.ru|apishops.ru)) {return 444;}
location ~* /(magmi.(php|ini)|uploadTester.asp|.*server.ca.pem|(x|humans).txt|(flashgallery|thumb_editor|html|phpinfo|xxx|bad).php|filezilla.xml)$ {return 444;}
if ($query_string ~* "^(.*)(/(.*my.cnf|self/environ|etc/passwd)|cmd=|curl+|bad.php)(.*)$") {return 444;}
location ~* .*/(fckeditor|kcfinder|ckfinder)/.*\.(php|asp(|x))$ {return 444;}
location ~ /\.ht {return 444;}

add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block;";

location ~* /((wp-config|plugin_upload|xmlrpc|wp-xmlrpc).php|(readme|license|changelog).(html|txt|md)|(debug|access|error)(.|_)log)$ {access_log off; log_not_found off;
return 404;}
location ~* /((dl-skin|nyemnyem|searchreplacedb2|wp-content|bogel|myluph|parser|routing).php|local.xml)$ {access_log off; log_not_found off; return 404;}
location ~* /.*((|.|%23)(wp-config|xmlrpc).*(php(_bak|~|#|%23)|txt|old|bak|save|orig(|inal)|swp|swo)).*$ {access_log off; log_not_found off; return 404;}
if ($query_string ~* "^(.*)(wp-config.php|dl-skin.php|xmlrpc.php|uploadify.php|admin-ajax.php|local.xml)(.*)$") {return 404;}
if ($query_string ~* "(concat.*\(|union.*select.*\(|union.*all.*select)") {return 404;}

if ($query_string ~* "author=[0-9]") {return 301 $scheme://$host/;}

location ~* /(?:uploads|files)/.*\.(php|cgi|py|pl)$ {return 404;}
location ~* /(wp|page)/.*wp-.*/.*$ {access_log off; log_not_found off; return 404;}

location = /wp-content/ {return 404;}            location = /wp-content {return 404;}
location = /wp-includes/ {return 404;}           location = /wp-includes {return 404;}
location = /wp-content/plugins/ {return 404;}    location = /wp-content/plugins {return 404;}
location = /wp-content/mu-plugins/ {return 404;} location = /wp-content/mu-plugins {return 404;}
location = /wp-content/uploads/ {return 404;}    location = /wp-content/uploads {return 404;}
location = /wp-content/themes/ {return 404;}     location = /wp-content/themes {return 404;}
location = /wp-content/languages/ {return 404;}         location = /wp-content/languages {return 404;}
location = /wp-content/languages/plugins/ {return 404;} location = /wp-content/languages/plugins {return 404;}
location = /wp-content/languages/themes/ {return 404;}  location = /wp-content/languages/themes {return 404;}
location ~ /wp-content/languages/(.+)\.(po|mo)$ {return 404;}

Теперь перезапускаем nginx и проверяем чтоб всё работало как надо с помощью, например, PageSpeed Insights:

$ /etc/rc.d/nginx restart

Настройка apache

Конфиг Apache теперь храниться по пути ~/etc/apache_2.4/httpd.conf, и для его перезапуска необходимо будет выполнить команду:

$ /etc/rc.d/httpd restart

Всё что нам необходимо сделать здесь - это добавить 2 строки перед секцией <VirtualHost *:8080>: ServerTokens Prod и ServerSignature Off, которые запрещают вывод сигнатур сервера. Если тебе потребуется ещё что-то настраивать у Apache - то делай это в этом файле.

Настройка php

Для того, чтоб веб-сервер Apache начал читать именно наш конфиг, а не тот что лежит в директории ~/etc/ - нам необходимо скопировать соответствующий в домашнюю директорию. Поясню - например, мы используем php версии 5.3. Смотрим в ~/etc/:

$ ls -l ~/etc/php*
lrwxr-xr-x  1 root  wheel    22 Jan 18 16:55 /home/your_domain/etc/php -> ../../../usr/opt/php53
lrwxr-xr-x  1 root  wheel     9 Jan 18 16:55 /home/your_domain/etc/php.ini -> php53.ini
-rw-r--r--  1 root  wheel  1106 Jan 18 16:59 /home/your_domain/etc/php53.ini
-rw-r--r--  1 root  wheel  1156 Jan 18 08:26 /home/your_domain/etc/php56.ini

Конфиг есть, но писать в него мы соответственно не можем. Копируем его в домашнюю директорию под именем php.ini:

$ cp ~/etc/php53.ini ~/php.ini

И добавляем в него строку expose_php=Off, которая скроет версию используемого php и заголовках ответов веб-сервера. Перезапускаем Apache:

$ /etc/rc.d/httpd restart

И проверяем чтоб всё работало, но ничего лишнего наружу “не светилось”.

Настраиваем резервное копирование

Хоть администраторы хостинга и выполняют резервное копирование - но лишней копия всё же не будет. Хранить мы будем бэкапы 31 день (все настройки указываются в начале скрипта), не забудь указать настройки ID хостинга (т.е. названия твоей домашней директории) и реквизиты для подключения к mysql базе примерно в 84 строке скрипта (+ там же проверка на наличие итогового бэкапа бд):

#!/bin/bash
## @author    Paramtamtam
## @project   Nic.ru backup script
## @copyright 2014 <https: //github.com/tarampampam>
## @github    https://github.com/tarampampam/nic.ru-bascup-script/
## @version   0.1.3
##
## @depends   mysqldump, tar

# *****************************************************************************
# ***                               Config                                   **
# *****************************************************************************

## nic.ru hosting id, look in 'cd ~ && pwd', ex.:
## [%YourID%@web2006 ~]$ cd ~ && pwd
## /home/%YourID%
HostingID="your_domain";
## Path to home dir, not need in change
PathToHomeDir=/home/$HostingID
## Path to directory, where backups will stored
PathToBackupsDir=$PathToHomeDir/backups
## Path to directory, where store DataBase dumps (add to backup file, and
##   remove from file system), not need in change
PathToDatabaseDumps=$PathToHomeDir/database-backup
##
## !!! IMPORTANT !!!
## Add your login, password and db_name to 'mysqldump' (line ~84)
## !!! IMPORTANT !!!
##
## Days count for backup files store, not need in change
StoreBackupsDaysCount=31

# *****************************************************************************
# ***                            END Config                                  **
# *****************************************************************************

## Found here - http://goo.gl/4Oi5ZK
cRed='e[1;31m'; cGreen='e[0;32m'; cNone='e[0m'; cYel='e[1;33m';
cBlue='e[1;34m'; cGray='e[1;30m'; cWhite='e[1;37m';

## Helpers Functions ###############################################

logmessage() {
  ## $1 = (not required) '-n' flag for echo output
  ## $2 = message to output

  flag=''; outtext='';
  if [ "$1" == "-n" ]; then
    flag="-n "; outtext=$2;
  else
    outtext=$1;
  fi

  echo -e $flag[$(date +%H:%M:%S)] "$outtext";
}

## Begin work ####################################################

# Create directory for backups (if not exists)
if [ ! -d $PathToBackupsDir ]; then
  logmessage -n "Create $PathToBackupsDir.. ";
  mkdir -p $PathToBackupsDir;
  if [ -d $PathToBackupsDir ]; then
    echo -e "Ok";
  else
    echo -e "Error";
    exit 1;
  fi
fi

# Clean temp dumps $PathToDatabaseDumps + create it (ex: broken last run)
logmessage -n "Clean and prepare $PathToDatabaseDumps.. ";
rm -R -f $PathToDatabaseDumps;
mkdir -p $PathToDatabaseDumps;
if [ -d $PathToBackupsDir ]; then
  echo -e "Ok";
else
  echo -e "Error";
fi

logmessage -n "Backup DataBase(s) to $PathToDatabaseDumps.. "
mysqldump --force --opt --add-locks --user=your_domain_mysql -pXXXXXXX --databases your_domain_db > $PathToDatabaseDumps/your_domain_db.sql
# Write here all files to check exists
if [ -f $PathToDatabaseDumps/your_domain_db.sql ]; then
  echo -e "Complete";
else
  echo -e "Error";
fi


cd $PathToBackupsDir
thisBackupFileName=backup-$(date +%y-%m-%d--%H-%M)-$HostingID.tar.bz2

logmessage -n "Pack files to $PathToBackupsDir/$thisBackupFileName.. "
tar -cpPjf $PathToBackupsDir/$thisBackupFileName \
    --exclude=$PathToBackupsDir* \
    --exclude=$PathToHomeDir/tmp/* \
    --exclude=*httpd.core \
    $PathToHomeDir;
echo -e "Complete";

# Make some clean
logmessage -n "Make some clean.. ";
rm -R -f $PathToDatabaseDumps;
echo -e "Complete";

sleep 2;

## Finish work ####################################################

logmessage -n "Deleting old backups from $PathToBackupsDir.. "
find $PathToBackupsDir -type f -mtime +$StoreBackupsDaysCount -exec rm '{}' \;
for FILE in $(find $PathToBackupsDir -mtime +$StoreBackupsDaysCount -type f); do
  logmessage "Delete $FILE as Old";
  rm -f $FILE;
done;
echo -e "Complete";

По итогу его выполнения у нас должна появиться директория ~/backups с текущим бэкапом всех данных. Для автоматизации в “Планировщик заданий” добавь задание вида /home/your_domain/scripts/backup.sh с запуском 1 раз в полночь, например.