Debugging PHP cURL SSL Timeouts

Server Provisioning

I rent a new server. The provider gives me an IP address. The address is public. The OS is Ubuntu 22.04. I open my local terminal. I use the SSH client. I type ssh root@10.0.0.5. I press enter. The server asks for my SSH key password. I type the password. I press enter. I see the server prompt.

I update the package list. I type apt update. The system connects to the Ubuntu servers. The system downloads the lists. I wait ten seconds. The prompt returns. I upgrade the software. I type apt upgrade -y. The system downloads new packages. The system installs the packages. I wait two minutes. The prompt returns.

I set the timezone. I type timedatectl set-timezone UTC. I create a non-root user. I type adduser sysadmin. I type a password. I confirm the password. I skip the user details. I press enter. I add the user to the sudo group. I type usermod -aG sudo sysadmin. I switch to the new user. I type su - sysadmin. I clear the screen. I type clear.

Firewall Configuration

I configure the firewall. I use UFW. The tool is standard. I type sudo ufw default deny incoming. I type sudo ufw default allow outgoing. I allow SSH traffic. I type sudo ufw allow 22/tcp. I allow HTTP traffic. I type sudo ufw allow 80/tcp. I allow HTTPS traffic. I type sudo ufw allow 443/tcp. I enable the firewall. I type sudo ufw enable. The system asks for confirmation. I type y. I press enter. The firewall starts. I check the status. I type sudo ufw status numeric. I see port 22. I see port 80. I see port 443. The rules are correct.

Database Installation

I install the database. I use MariaDB. I type sudo apt install mariadb-server -y. The system downloads the files. The system installs the database. I wait thirty seconds. I check the service. I type systemctl status mariadb. The service is active. I press q to exit the status screen.

I secure the database. I run the security script. I type sudo mysql_secure_installation. The script asks for the root password. The password is empty. I press enter. The script asks to switch to unix_socket authentication. I type n. The script asks to change the root password. I type y. I type a new password. I type it again. The script asks to remove anonymous users. I type y. The script asks to disallow root login remotely. I type y. The script asks to remove the test database. I type y. The script asks to reload privilege tables. I type y. The script finishes.

I create a database for the website. I open the database console. I type sudo mysql -u root -p. I type the password. I see the MariaDB prompt. I type CREATE DATABASE saas_db;. I press enter. I create a user. I type CREATE USER 'saas_user'@'localhost' IDENTIFIED BY 'hardpassword123';. I press enter. I grant permissions. I type GRANT ALL PRIVILEGES ON saas_db.* TO 'saas_user'@'localhost';. I press enter. I flush the privileges. I type FLUSH PRIVILEGES;. I exit the console. I type exit.

PHP Installation

I install PHP. I need version 8.2. Ubuntu 22.04 has PHP 8.1 by default. I add a repository. I type sudo add-apt-repository ppa:ondrej/php -y. The system updates the lists. I install the packages. I type sudo apt install php8.2-fpm php8.2-mysql php8.2-curl php8.2-xml php8.2-mbstring php8.2-zip php8.2-intl -y. The system installs the core. The system installs the modules.

I configure the PHP memory limit. I open the config file. I type sudo nano /etc/php/8.2/fpm/php.ini. I press Ctrl+W. I type memory_limit. I press enter. I see the line. The value is 128M. I change it. I type 256M. I press Ctrl+W. I type upload_max_filesize. I press enter. I change the value. I type 64M. I press Ctrl+W. I type post_max_size. I press enter. I change the value. I type 64M. I save the file. I press Ctrl+O. I press enter. I exit the editor. I press Ctrl+X.

I configure the PHP process manager. I open the pool file. I type sudo nano /etc/php/8.2/fpm/pool.d/www.conf. I scroll down. I find pm = dynamic. I keep it. I find pm.max_children. I change it to 20. I find pm.start_servers. I change it to 2. I find pm.min_spare_servers. I change it to 1. I find pm.max_spare_servers. I change it to 3. I save the file. I close the editor. I restart the PHP service. I type sudo systemctl restart php8.2-fpm.

Web Server Installation

I install Nginx. I type sudo apt install nginx -y. The system installs the package. I check the status. I type systemctl status nginx. The service is running.

I create a folder for the website. I type sudo mkdir -p /var/www/saas. I change the owner. I type sudo chown -R www-data:www-data /var/www/saas. I change the permissions. I type sudo chmod -R 755 /var/www/saas.

I configure the Nginx virtual host. I create a new file. I type sudo nano /etc/nginx/sites-available/saas.conf. I add the server block. I type server {. I press enter. I type listen 80;. I press enter. I type listen [::]:80;. I press enter. I type server_name my-saas-site.com;. I press enter. I type root /var/www/saas;. I press enter. I type index index.php index.html index.htm;. I press enter.

I add the main location block. I type location / {. I press enter. I type try_files $uri $uri/ /index.php?$args;. I press enter. I type }. I press enter.

I add the PHP location block. I type location ~ \.php$ {. I press enter. I type include snippets/fastcgi-php.conf;. I press enter. I type fastcgi_pass unix:/run/php/php8.2-fpm.sock;. I press enter. I type }. I press enter. I close the server block. I type }. I save the file. I close the editor.

I enable the site. I create a symlink. I type sudo ln -s /etc/nginx/sites-available/saas.conf /etc/nginx/sites-enabled/. I remove the default site. I type sudo rm /etc/nginx/sites-enabled/default. I test the configuration. I type sudo nginx -t. The output says syntax is ok. I reload Nginx. I type sudo systemctl reload nginx.

WordPress Core Setup

I download WordPress. I go to the web folder. I type cd /var/www/saas. I use wget. I type sudo wget https://wordpress.org/latest.tar.gz. The file downloads. I extract the file. I type sudo tar -xzf latest.tar.gz. The command creates a folder named wordpress. I move the files. I type sudo mv wordpress/* .. I delete the empty folder. I type sudo rm -rf wordpress. I delete the archive. I type sudo rm latest.tar.gz.

I configure WordPress. I copy the sample config. I type sudo cp wp-config-sample.php wp-config.php. I edit the file. I type sudo nano wp-config.php. I find the database name line. I change database_name_here to saas_db. I find the database user line. I change username_here to saas_user. I find the database password line. I change password_here to hardpassword123. I save the file. I close the editor. I fix the ownership. I type sudo chown -R www-data:www-data /var/www/saas.

Theme Deployment

A client builds a new business. The client needs a clean design. The client selects a template. The template is the Samu - SaaS / Software WordPress Theme. The client purchases the file. The client sends me the zip archive. I upload the archive to the server. I use the SCP command from my local machine. I type scp samu.zip sysadmin@10.0.0.5:/home/sysadmin/. The file transfers.

I return to the server terminal. I move the file. I type sudo mv /home/sysadmin/samu.zip /var/www/saas/wp-content/themes/. I go to the themes folder. I type cd /var/www/saas/wp-content/themes/. I unzip the file. I type sudo unzip samu.zip. The command creates a folder named samu. I delete the zip file. I type sudo rm samu.zip. I set the folder owner. I type sudo chown -R www-data:www-data samu.

The client logs into the WordPress admin panel. The client goes to Appearance. The client clicks Themes. The client activates the Samu theme. The client adds pages. The client adds text. The site goes live.

The Observation

I check the site three days later. I open my browser. I load the homepage. The page loads. I scroll down. I see a blank white box. The box is in the footer. The box should show the current server status of the SaaS product. The text is missing. The layout is broken.

I do not see an error message. I do not get an alert from the monitoring system. The CPU usage is normal. The memory usage is normal. The site responds fast. But one specific block fails to render.

I look at the server logs. I type sudo tail -n 50 /var/log/nginx/error.log. The file is empty. I type sudo tail -n 50 /var/var/log/nginx/access.log. I see normal HTTP 200 codes. I check the PHP logs. I edit the pool file earlier. I did not enable the PHP error log. I enable it now. I type sudo nano /etc/php/8.2/fpm/pool.d/www.conf. I add a line. I type php_admin_value[error_log] = /var/log/php-fpm-error.log. I save the file. I type sudo systemctl restart php8.2-fpm. I reload the webpage. I check the new log. I type cat /var/log/php-fpm-error.log. The file does not exist. PHP does not log an error.

Code Inspection

I need to read the theme code. Developers build many products. Users go online and Download WordPress Themes. Some themes use external APIs. I search the theme folder. I type cd /var/www/saas/wp-content/themes/samu. I use the grep command. I look for remote requests. I type grep -rn "wp_remote_get" ..

The command finds a match. The match is in a file named inc/class-status-widget.php. I open the file. I type nano inc/class-status-widget.php. I look at the code. I find a function. The function name is fetch_api_status. The function calls wp_remote_get. The URL is https://api.system-monitor-service.com/v1/status. The code assigns the result to a variable. The code checks for a WordPress error object. If the result is an error, the code returns an empty string. The code does not print the error. The frontend shows a blank box.

I modify the code temporarily. I want to see the error. I find the error check block. I add a line. I type error_log( $response->get_error_message() );. I save the file. I close the editor. I reload the frontend page in my browser. I wait. The page loads. I check the server error log. I type sudo cat /var/log/php-fpm-error.log. I see a line. The line says cURL error 28: Connection timed out after 5000 milliseconds.

Manual Network Testing

The PHP script fails to connect. The timeout is 5 seconds. I test the connection from the command line. I use curl. I type curl -v https://api.system-monitor-service.com/v1/status. The -v flag means verbose. The command prints debug text.

I read the output. The system resolves the domain name. The domain has an IPv4 address. The IPv4 address is 192.0.2.10. The domain has an IPv6 address. The IPv6 address is 2001:db8::10. The curl tool prefers IPv6. The curl tool tries to connect to 2001:db8::10. The port is 443. The command hangs. The screen does not move. I wait. After two minutes, the command fails. The error is Connection timed out.

I force curl to use IPv4. I use the -4 flag. I type curl -v -4 https://api.system-monitor-service.com/v1/status. I press enter. The command executes immediately. The connection is successful. The SSL handshake completes. The server returns HTTP 200. The server returns a JSON string. The API works perfectly over IPv4. The API fails completely over IPv6.

Network Packet Tracing

I need to see the network traffic. I use tcpdump. I type sudo apt install tcpdump -y. I find my network interface name. I type ip a. The main interface is eth0.

I run tcpdump. I filter for the destination IPv6 address. I type sudo tcpdump -i eth0 -n host 2001:db8::10. I leave the command running. I open a second terminal window. I connect to the server again. I use the second window to run curl. I type curl -v -6 https://api.system-monitor-service.com/v1/status.

I watch the first window. I see the output. The server sends an IP packet. The protocol is TCP. The source is my server IPv6 address. The destination is 2001:db8::10. The flags show [S]. This means SYN. The server asks to start a connection. I wait. The server sends another SYN packet. I wait. The server sends a third SYN packet. I do not see any incoming packets. I do not see a [S.] packet. A [S.] packet means SYN-ACK. The remote server does not answer. Or the answer does not reach my server. I stop tcpdump. I press Ctrl+C.

Routing Table Analysis

I check the IPv6 configuration on my server. I type ip -6 address. I see the eth0 interface. The interface has a public IPv6 address. The address scope is global. The address is 2001:db8:1::50. The configuration looks correct.

I check the IPv6 routing table. I type ip -6 route. The output shows the default route. The line says default via 2001:db8:1::1 dev eth0 metric 100. The gateway is 2001:db8:1::1.

I try to ping the gateway. I use ping6. I type ping6 -c 4 2001:db8:1::1. The command sends four ICMP packets. The command waits. The output says Destination unreachable: Address unreachable. The server cannot talk to its own gateway.

I check the neighbor cache. In IPv4, this is the ARP table. In IPv6, this is the NDP table. I type ip -6 neigh. I look at the line for the gateway. The line says 2001:db8:1::1 dev eth0 FAILED. The server sends neighbor solicitation messages. The router does not send neighbor advertisement messages. The MAC address is unknown. The network provider has a broken IPv6 configuration in this datacenter.

The Resolution

I cannot fix the provider network. I must bypass the problem. I can disable IPv6 on the entire server. I edit the sysctl file. I type sudo nano /etc/sysctl.conf. I scroll to the bottom. I add a line. I type net.ipv6.conf.all.disable_ipv6 = 1. I add another line. I type net.ipv6.conf.default.disable_ipv6 = 1. I add a third line. I type net.ipv6.conf.lo.disable_ipv6 = 1. I save the file. I close the editor.

I apply the sysctl changes. I type sudo sysctl -p. The system reads the file. The system applies the variables. I check the network interfaces. I type ip a. I look at eth0. The inet6 line is gone. The server no longer has an IPv6 address.

I edit the Nginx configuration. Nginx still tries to bind to the IPv6 port. I open the site config. I type sudo nano /etc/nginx/sites-available/saas.conf. I find the line listen [::]:80;. I delete the line. I save the file. I restart Nginx. I type sudo systemctl restart nginx.

I run the curl test again. I do not use the -4 flag. I type curl -v https://api.system-monitor-service.com/v1/status. The curl tool looks up the domain. The tool finds the IPv4 and IPv6 addresses. The server has no IPv6 interface. The tool immediately falls back to IPv4. The connection succeeds instantly. The JSON data prints on the screen.

I go back to my web browser. I reload the client website. I scroll down to the footer. The blank box is gone. The widget displays the status. The text says "All Systems Operational". The PHP script connects via IPv4. The script retrieves the data. The script renders the HTML. I open the theme file again. I type nano /var/www/saas/wp-content/themes/samu/inc/class-status-widget.php. I find the error_log line. I delete the line. I save the file. I close the editor. I close the SSH connection. I type exit.

评论 0