11.02.2022
Instalacja i konfiguracja strony internetowej na VPS z Ubuntu 20.04 LTS
Bazy danychKonfiguracjaLinux | UnixPrywatneWWW
stronainternetowanavps

Ostatnia aktualizacja: 25.04.2023 r.

Jeśli chciałbyś(-abyś), podobnie jak ja, mieć swoją stronę internetową lub w swojej pracy masz za zadanie przygotować grunt pod firmową stronę internetową czy też aplikację internetową, to poniżej udostępniam Ci wszystkie kroki, które ja zrobiłem w celu uruchomienia strony internetowej na Ubuntu 20.04 LTS, na której właśnie jesteś. 😎

Parametry VPS

  • 1 rdzeń CPU
  • 2 GB RAM
  • 20 GB NVMe SSD

Przed przystąpieniem do konfiguracji w pierwszej kolejności aktualizuję system i pakiety do najnowszych dostępnych wersji.

sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get dist-upgrade -y && sudo apt-get autoremove -y

SSH

  1. Przygotowuję klucz z hasłem (poniższe polecenie wykonałem na Windows 10).
    ssh-keygen -t rsa -b 4096
  2. Loguję się do VPS`a, a następnie tworzę katalog dla kluczy publicznych, w katalogu swojego konta.
    mkdir -p ~/.ssh
  3. Kopiuję klucz publiczny do pliku authorized_keys.
    echo "klucz_publiczny" >> ~/.ssh/authorized_keys
  4. Ustawiam rekurencyjnie właściciela i grupę na swoje konto.
    chown -R grzegorz_wita:grzegorz_wita ~/.ssh
  5. Ustawiam uprawnienia dla katalogu.
    chmod 700 ~/.ssh
  6. Ustawiam uprawnienia dla pliku.
    chmod 600 ~/.ssh/authorized_keys
  7. Aktualizuję konfigurację SSH i modyfikuję poniższe pozycje.
    sudo nano /etc/ssh/sshd_config
    [...]
    Port 22122
    [...]
    PermitRootLogin no
    [...]
    PubkeyAuthentication yes
    [...]
    AuthorizedKeysFile .ssh/authorized_keys
    [...]
    PasswordAuthentication no
    PermitEmptyPasswords no
    [...]
    
  8. Udostępniam port SSH na zaporze sieciowej.
    sudo ufw allow 22122/SSH
  9. Wyświetlam numeryczną listę reguł.
    sudo ufw status numbered
  10. Usuwam domyślą regułę SSH.
    sudo ufw delete 22/tcp numer
  11. Restartuję usługę SSH.
    sudo systemctl restart ssh

Apache

  1. Instaluję wymagane pakiety (plus p7zip-full do obsługi formatu 7z na potrzeby kopii zapasowych).
    sudo apt-get install apache2 p7zip-full php libapache2-mod-php php-mysql -y
  2. Udostępniam porty Apache wraz ze sprawdzeniem.
    sudo ufw allow in "Apache Full" && sudo ufw status verbose
  3. Tworzę katalog dla strony internetowej.
    sudo mkdir /var/www/grzegorzwitait
  4. Ustawiam rekurencyjnie odpowiedniego użytkownika i grupę.
    sudo chown -R www-data:www-data /var/www/grzegorzwitait
  5. Ustawiam odpowiednie uprawnienia.
    sudo chmod -R g+rw /var/www/grzegorzwitait
  6. Tworzę konfigurację strony dla Apache`a.
    sudo nano /etc/apache2/sites-available/grzegorzwitait.conf
    <VirtualHost *:80>
        ServerName grzegorzwita.it
        ServerAlias *.grzegorzwita.it
        ServerAdmin admin@grzegorzwita.it
        
        RewriteEngine On
        RewriteCond %{HTTP:X-Forwarded-Proto} !https [NC]
        RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,QSA,R=permanent]
    </VirtualHost>
    
    <IfModule mod_ssl.c>
    SSLStaplingCache "shmcb:/var/log/ssl_stapling(150000)"
    <VirtualHost *:443>
        Protocols h2 http/1.1
        ServerName grzegorzwita.it
        ServerAlias *.grzegorzwita.it
        ServerAdmin admin@grzegorzwita.it
    
        DocumentRoot /var/www/grzegorzwitait
        DirectoryIndex index.php
    
        <Directory /var/www/grzegorzwitait>
            Options -Indexes -Includes +FollowSymLinks +MultiViews
    	AllowOverride all
    	Require all granted
        </Directory>
        
        ErrorLog ${APACHE_LOG_DIR}/error.log
        CustomLog ${APACHE_LOG_DIR}/access.log combined
    
        Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
        Header always set X-Frame-Options "SAMEORIGIN"
        Header always set X-XSS-Protection "1; mode=block"
        Header always set X-Content-Type-Options "nosniff"
        Header always set Referrer-Policy "strict-origin-when-cross-origin"
        Header always set Content-Security-Policy "form-action 'self'; base-uri 'self'; frame-ancestors 'self'; object-src 'none'; worker-src 'self'; child-src 'self'; frame-src 'self'; upgrade-insecure-requests"
    
        SSLEngine on
        SSLProtocol -all +TLSv1.2 +TLSv1.3
        SSLCipherSuite EECDH+AESGCM:EDH+AESGCM
        SSLOpenSSLConfCmd Curves X25519:secp521r1:secp384r1:prime256v1
        SSLOpenSSLConfCmd Options -SessionTicket,ServerPreference    
        SSLHonorCipherOrder off
        SSLSessionTickets off
        SSLCompression off
        SSLUseStapling on
        Include /etc/letsencrypt/options-ssl-apache.conf
        SSLCertificateFile /etc/letsencrypt/live/grzegorzwita.it/fullchain.pem
        SSLCertificateKeyFile /etc/letsencrypt/live/grzegorzwita.it/privkey.pem
    </VirtualHost>
    </IfModule>
    
  7. Włączam konfigurację strony.
    sudo a2ensite grzegorzwitait
  8. Wyłączam konfigurację domyślnej strony.
    sudo a2dissite 000-default
  9. Sprawdzam konfigurację Apache`a.
    sudo apache2ctl configtest
  10. Włączam określone moduły Apache`a.
    sudo a2enmod rewrite headers http2 mpm_event proxy_fcgi
  11. Wyłączam niektóre moduły Apache`a.
    sudo a2dismod php7.4 mpm_prefork
  12. Włączam konfigurację modułu php7.4-fpm.
    sudo a2enconf php7.4-fpm
  13. Dopieszczam konfigurację PHP dla Apache`a, FPM i CLI.
    sudo nano /etc/php/7.4/apache2/php.ini
    sudo nano /etc/php/7.4/fpm/php.ini
    sudo nano /etc/php/7.4/cli/php.ini
    [...]
    max_execution_time = 60
    [...]
    max_input_vars = 2000
    [...]
    post_max_size = 16M
    [...]
    upload_max_filesize = 5M
    [...]
    max_file_uploads = 40
    [...]
    
  14. Restartuję usługę Apache`a.
    sudo systemctl reload apache2

MySQL

  1. Instaluję serwer MySQL.
    sudo apt-get install mysql-server -y
  2. Uruchamiam konfigurator serwera MySQL.
    sudo mysql_secure_installation
  3. Loguję się do serwera MySQL.
    sudo mysql -u root -p
  4. Tworzę bazę danych dla strony internetowej.
    CREATE DATABASE grzegorzwitait_dba;
  5. Tworzę użytkownika do bazy danych dla strony internetowej.
    CREATE USER 'grzegorzwitait_dba'@'localhost' IDENTIFIED WITH caching_sha2_password BY 'Sk0mp1ikow4N3_Ha$LO';
  6. Nadaję odpowiednie uprawnienia utworzonemu użytkownikowi.
    GRANT ALL ON grzegorzwitait_dba.* TO 'grzegorzwitait_dba'@'localhost';
  7. Wprowadzam zmiany w życie.
    FLUSH PRIVILEGES;
  8. Wychodzę z serwera bazy danych.
    exit
  9. Loguję się danymi użytkownika utworzonymi wyżej.
    sudo mysql -u grzegorzwitait_dba -p
  10. Sprawdzam, czy konto ma dostęp tylko do wyżej utworzonej bazy danych.
    SHOW DATABASES;
  11. Wychodzę z serwera bazy danych.
    exit

phpmyadmin

  1. Loguję się do bazy danych.
    sudo mysql -u root -p
  2. Tworzę bazę danych dla potrzeb phpmyadmin.
    CREATE DATABASE grzegorzwitait_pma;
  3. Tworzę użytkownika do bazy danych dla phpmyadmin.
    CREATE USER 'grzegorzwitait_pma'@'localhost' IDENTIFIED WITH caching_sha2_password BY 'Sk0mp1ikow4N3_Ha$LO';
  4. Nadaję odpowiednie uprawnienia.
    GRANT ALL ON grzegorzwitait_pma.* TO 'grzegorzwitait_pma'@'localhost';
  5. Usuwam komponent.
    UNINSTALL COMPONENT "file://component_validate_password";
  6. Wychodzę z serwera bazy danych.
    exit
  7. Instaluję odpowiednie pakiety.
    sudo apt-get install phpmyadmin php-mbstring php-zip php-gd php-json php-curl -y
  8. Loguję się do bazy danych.
    sudo mysql -u root -p
  9. Instaluję wyżej usunięty komponent.
    INSTALL COMPONENT "file://component_validate_password";
  10. Wychodzę z serwera bazy danych.
    exit
  11. Aktualizuję konfigurację phpmyadmin.
    sudo nano /etc/phpmyadmin/config.inc.php
    [...]
    /* User for advanced features */
    
    $cfg['Servers'][$i]['controluser'] = 'grzegorzwitait_pma';
    $cfg['Servers'][$i]['controlpass'] = 'Sk0mp1ikow4N3_Ha$LO';
    [...]
    
  12. Aktywuję moduł mbstring w PHP.
    sudo phpenmod mbstring
  13. Dopieszczam konfigurację phpmyadmin w Apache.
    sudo nano /etc/apache2/conf-available/phpmyadmin.conf
    <Directory /usr/share/phpmyadmin>
        [. . .]
        AllowOverride All
    
        [. . .]
    </Directory>
    
  14. Restartuję usługę Apache.
    sudo systemctl restart apache2
  15. Opcjonalnie mogę ustawić dodatkowe "zabezpieczenie" w dostępie do phpmyadmin polegające na ustawieniu dodatkowego logowania do strony logowania phpmyadmin.
    sudo nano /usr/share/phpmyadmin/.htaccess
    AuthType Basic
    AuthName "Restricted Files"
    AuthUserFile /etc/phpmyadmin/.htpasswd
    Require valid-user
    
    sudo htpasswd -bc /etc/phpmyadmin/.htpasswd N4zW@_Uzy7k0wn1ka N1gdy_nie-zgadni3szhaha
    sudo systemctl restart apache2

FTP

  1. Instaluję odpowiednie pakiety.
    sudo apt-get install vsftpd -y
  2. Sprawdzam status usługi.
    sudo systemctl status vsftpd
  3. Udostępniam odpowiednie porty w zaporze sieciowej.
    sudo ufw allow 20/tcp
    sudo ufw allow 21/tcp
    sudo ufw allow 40000:50000/tcp
  4. Dopieszczam konfigurację.
    sudo nano /etc/vsftpd.conf
    [...]
    listen=NO
    [...]
    listen_ipv6=NO
    [...]
    anonymous_enable=NO
    [...]
    local_enable=YES
    [...]
    write_enable=YES
    [...]
    local_umask=022
    [...]
    dirmessage_enable=YES
    [...]
    use_localtime=YES
    [...]
    xferlog_enable=YES
    [...]
    connect_from_port_20=YES
    [...]
    pam_service_name=vsftpd
    [...]
    force_dot_files=YES
    pasv_min_port=40000
    pasv_max_port=50000
    
  5. Restartuję usługę.
    sudo systemctl restart vsftpd
  6. Dodaję użytkownika.
    sudo adduser --home /var/www --ingroup www-data nazwa_uzytkownika N1gdy_nie-zgadni3szhaha
  7. Dodaję rekurencyjnie odpowiednią grupę do katalogu.
    sudo chgrp -R www-data /var/www
  8. Zmieniam rekurencyjnie uprawnienia dla grupy.
    sudo chmod -R g+w /var/www

SSL

  1. Instaluję odpowiednie pakiety.
    sudo apt-get install certbot python3-certbot-apache -y
  2. Generuję certyfikat Wildcard i postępuję zgodnie z informacjami wyświetlanymi na ekranie.
    sudo certbot certonly --manual --preferred-challenges=dns --email admin@grzegorzwita.it --server https://acme-v02.api.letsencrypt.org/directory --agree-tos -d "*.grzegorzwita.it, grzegorzwita.it"
  3. Sprawdzam certyfikaty.
    sudo certbot certificates
  4. Przygotowuję skrypt, który będę wykorzystywał przy odświeżaniu certyfikatu (to samo polecenie co w pkt. 2, dla mnie wygodniej, gdyż nie będę musiał przeklikiwać się przez historię basha).
    sudo nano /root/letsencrypt_renew.sh
    #!/bin/bash
    sudo certbot certonly --manual --preferred-challenges=dns --email admin@grzegorzwita.it --server https://acme-v02.api.letsencrypt.org/directory --agree-tos -d "*.grzegorzwita.it, grzegorzwita.it"
    sudo systemctl reload apache2
  5. Weryfikuję zabezpieczenia strony na https://www.ssllabs.com/ssltest/.

Bookstack

Krok opcjonalny ponieważ prócz głównej strony przygotowałem sobie dodatkową platformę do przechowywania przydatnych informacji. Dodatkowo twórca zautomatyzował cały proces instalacji i wystarczy pobrać gotowy skrypt instalacyjny, jednak jest on dedykowany dla "pustych systemów" więc w moim przypadku nie mogłem ich wykorzystać i musiałem pójść trudniejszą drogą. 😋

  1. Tworzę katalog i przechodzę do niego.
    mkdir /var/www/BookStack && cd /var/www/BookStack
  2. Klonuję repozytorium z GitHub`a.
    git clone https://github.com/BookStackApp/BookStack.git --branch release --single-branch
  3. Instaluję odpowiednie pakiety.
    sudo apt-get install unzip php-fpm php-ldap php-tidy -y
  4. Instaluję composer.
    sudo composer install --no-dev
  5. Ustawiam rekurencyjnie odpowiedniego właściciela i grupę dla odpowiednich katalogów.
    sudo chown -R www-data:www-data bootstrap/cache public/uploads storage
  6. Ustawiam rekurencyjnie odpowiednie uprawnienia.
    sudo chmod -R g+rw bootstrap/cache public/uploads storage
  7. Loguję się do bazy danych.
    sudo mysql -u root -p
  8. Tworzę bazę danych dla potrzeb Bookstack`a.
    CREATE DATABASE bookstack;
  9. Tworzę użytkownika do bazy danych dla Bookstack`a.
    CREATE USER 'bookstack'@'localhost' IDENTIFIED WITH caching_sha2_password BY 'Sk0mp1ikow4N3_Ha$LO';
  10. Nadaję odpowiednie uprawnienia.
    GRANT ALL ON bookstack.* TO 'bookstack'@'localhost'  WITH GRANT OPTION;
  11. Odświerzam uprawnienia na serwerze MySQL, chociaż nie jest to wymagane, gdyż uprawnienia przyznane za pomocą opcji GRANT nie wymagają opcji FLUSH PRIVILEGES, aby zaczęły obowiązywać - serwer MySQL zauważy te zmiany i natychmiast przeładuje tabele. Jak profilaktycznie to wykonuję.
    FLUSH PRIVILEGES;
  12. Wychodzę z serwera bazy danych.
    exit
  13. Kopiuję domyślny plik ustawień i wypełniam go danymi logowania do bazy danych oraz danymi serwera poczty e-mail.
    sudo cp .env.example .env
  14. Generuję unikatowy klucz aplikacji i go kopiuję.
    php artisan key:generate
  15. Wchodzę w plik ustawień w celu w celu umieszczenia w nim wygenerowanego klucza.
    sudo nano .env
  16. Uruchamiam migrację bazy danych.
    php artisan migrate
  17. Tworzę konfigurację strony dla Apache`a.
    sudo nano /etc/apache2/sites-available/bookstack.conf
    <VirtualHost *:80>
        ServerName zycie.grzegorzwita.it
        ServerAdmin admin@grzegorzwita.it
        Redirect / https://zycie.grzegorzwita.it
    </VirtualHost>
    
    <VirtualHost *:443>
        ServerName zycie.grzegorzwita.it
        ServerAdmin admin@grzegorzwita.it
        DocumentRoot /var/www/BookStack/public/
        <Directory /var/www/BookStack/public/>
            Options Indexes FollowSymLinks
            AllowOverride None
            Require all granted
            <IfModule mod_rewrite.c>
                <IfModule mod_negotiation.c>
                    Options -MultiViews -Indexes
                </IfModule>
                RewriteEngine On
                # Handle Authorization Header
                RewriteCond %{HTTP:Authorization} .
                RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
                # Redirect Trailing Slashes If Not A Folder...
                RewriteCond %{REQUEST_FILENAME} !-d
                RewriteCond %{REQUEST_URI} (.+)/$
                RewriteRule ^ %1 [L,R=301]
                # Handle Front Controller...
                RewriteCond %{REQUEST_FILENAME} !-d
                RewriteCond %{REQUEST_FILENAME} !-f
                RewriteRule ^ index.php [L]
            </IfModule>
        </Directory>
        ErrorLog ${APACHE_LOG_DIR}/error_bookstack.log
        CustomLog ${APACHE_LOG_DIR}/access_bookstack.log combined
        
        RewriteEngine On
    
        Include /etc/letsencrypt/options-ssl-apache.conf
        SSLCertificateFile /etc/letsencrypt/live/grzegorzwita.it/fullchain.pem
        SSLCertificateKeyFile /etc/letsencrypt/live/grzegorzwita.it/privkey.pem
    </VirtualHost>
    
  18. Włączam konfigurację strony i restartuję usługę Apache`a.
    sudo a2ensite bookstack
    sudo systemctl reload apache2
  19. BookStack`a aktualizuję w poniższy sposób.
    cd /var/www/BookStack/
    git pull origin release && composer install --no-dev && php artisan migrate && php artisan cache:clear && php artisan config:clear && php artisan view:clear

Kopia zapasowa

  1. Przygotowałem plik skryptu kopia_zapasowa.sh - w moim przypadku kopiuję główną stronę internetową, bootstacka z subdomeny oraz bazy danych - jeśli u Ciebie jest inaczej, to musisz zmodyfikować skrypt pod siebie.
    sudo nano /root/kopia_zapasowa.sh
    #!/bin/bash
    DATE=$(date +"%Y-%m-%d")
    
    mkdir -p /root/backup
    tar -caf - /var/www/grzegorzwitait -P | 7z a -si /var/www/grzegorzwitait.tar.7z >> /var/www/grzegorzwitait_$DATE.log 2>&1
    tar -caf - /var/www/BookStack -P | 7z a -si /var/www/BookStack.tar.7z >> /var/www/BookStack_$DATE.log 2>&1
    7z e /var/www/grzegorzwitait.tar.7z -o/var/www/ >> /var/www/grzegorzwitait_$DATE.log 2>&1
    7z e /var/www/BookStack.tar.7z -o/var/www/ >> /var/www/BookStack_$DATE.log 2>&1
    rm -f -R /var/www/grzegorzwitait.tar.7z
    rm -f -R /var/www/BookStack.tar.7z
    mv /var/www/grzegorzwitait.tar /var/www/grzegorzwitait_$DATE.tar >> /var/www/grzegorzwitait_$DATE.log 2>&1
    mv /var/www/BookStack.tar /var/www/BookStack_$DATE.tar >> /var/www/BookStack_$DATE.log 2>&1
    mysqldump --host=localhost --user=konto_bazy_danych --password='N1gdy_nie-zgadni3szhaha' grzegorzwitait > /var/www/MYSQL_$DATE.sql 2> /var/www/grzegorzwitait_$DATE.log
    mysqldump --host=localhost --user=konto_bazy_danych --password='N1gdy_nie-zgadni3szhaha' bookstack > /var/www/MYSQL_$DATE.sql 2> /var/www/BookStack_$DATE.log
    7z a -t7z -mhe=on -pN1gdy_nie-zgadni3szhaha -m0=lzma -mx=9 -mfb=64 -md=32m -ms=on /var/www/grzegorz_wita_$DATE.7z /var/www/*.tar /var/www/*.sql >> /var/www/grzegorz_wita_$DATE.log 2>&1
    mv /var/www/grzegorz_wita_$DATE.7z /root/backup
    mv /var/www/*_$DATE.log /root/backup
    find /root -maxdepth 1 -type f -name '*.7z'-delete
    find /root -maxdepth 1 -type f -name '*.log'-delete
  2. Ustawiam cron job`a, aby uruchamiał skrypt codziennie o 20:00.
    sudo crontab -e
    0 20 * * * /root/kopia_zapasowa.sh >> /home/grzegorz_wita/backup/kopia_zapasowa_`date +\%Y-\%m-\%d`.log 2>&1
0 komentarzy

Szybki kontakt

Masz pytania? Napisz