<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/">
	<channel>
		<title><![CDATA[AdminsForum - aaPanel Websites & Web Servers Guides]]></title>
		<link>https://adminsforum.com/</link>
		<description><![CDATA[AdminsForum - https://adminsforum.com]]></description>
		<pubDate>Thu, 15 Oct 2026 05:22:24 +0000</pubDate>
		<generator>MyBB</generator>
		<item>
			<title><![CDATA[Fix 429 Too Many Requests Errors in aaPanel]]></title>
			<link>https://adminsforum.com/Thread-guide-fix-429-too-many-requests-errors-in-aapanel</link>
			<pubDate>Thu, 15 Oct 2026 12:42:27 +0800</pubDate>
			<dc:creator><![CDATA[<a href="https://adminsforum.com/member.php?action=profile&uid=2">DEV</a>]]></dc:creator>
			<guid isPermaLink="false">https://adminsforum.com/Thread-guide-fix-429-too-many-requests-errors-in-aapanel</guid>
			<description><![CDATA[<span style="font-weight: bold;" class="mycode_b">A 429 Too Many Requests response usually means a request limit has been reached. Find the layer issuing the response before changing a setting: it may be your CDN, Nginx, a security plugin or the application itself.</span><br />
<br />
This guide demonstrates one Nginx limiter on a private lab endpoint. It does not assume every aaPanel website has that rule, and it does not recommend turning off protection to make an error disappear.<br />
<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://adminsforum.com/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=323" target="_blank" title="">nginx-429-20261015.jpg</a> (Size: 116.42 KB / Downloads: 4)
<!-- end: postbit_attachments_attachment --><br />
<span style="font-style: italic;" class="mycode_i">Original AdminsForum lab results, formatted as a transcript rather than a screenshot.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">1. Capture one safe example</span><br />
Record the URL path, time and timezone, action, response status and how often the client is sending requests. Does it affect everyone, one network, one account or only a particular API? A page can send many background requests even when a user clicks once.<br />
<br />
Inspect response headers in your browser's Network tab. If a Retry-After header is present, use its stated delay; do not assume every limiter supplies it. Avoid repeatedly submitting login forms, purchases or imports while diagnosing the problem. Save a redacted error example without session cookies, authorization headers or private query strings.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">2. Match the response to website logs</span><br />
In aaPanel open <span style="font-weight: bold;" class="mycode_b">Website → PHP Project</span>, find the affected site and select <span style="font-weight: bold;" class="mycode_b">Log</span>. Check <span style="font-weight: bold;" class="mycode_b">Access log</span> and <span style="font-weight: bold;" class="mycode_b">Error log</span> around the recorded time. Refresh and expand the displayed line count if needed. Labels may vary by panel version.<br />
<br />
Our lab site's logs are at:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/www/wwwlogs/adminslab.com.log
/www/wwwlogs/adminslab.com.error.log</code></div></div>Use the actual path configured for your own domain. A bounded SSH check is:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>tail -n 100 /www/wwwlogs/example.com.log
tail -n 100 /www/wwwlogs/example.com.error.log</code></div></div>A 429 entry confirms that this log recorded the response, but does not by itself identify the rule. If there is no matching origin request, check the CDN or earlier proxy's events and verify you selected the correct log. Application-generated 429 responses need application logs too.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">3. Recognize an Nginx rate-limit rejection</span><br />
Our test error log contained:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>limiting requests, excess: 0.991 by zone "af429"</code></div></div>The named zone was our own test limiter. Its name is not a built-in aaPanel setting. We also added a diagnostic access-log field in the isolated configuration and saw:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>200 PASSED
429 REJECTED
200 PASSED</code></div></div>Default website logs may not contain that extra field. Do not expect to find it unless your log format includes it. Nginx can use a different rejection status; seeing 503 instead of 429 does not rule out a request limiter.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">4. What the private experiment tested</span><br />
We ran a separate Nginx configuration listening only on the lab's loopback interface. One exact path served a harmless text fixture. Its illustrative limit allowed one request per second with no extra burst allowance, and we explicitly selected 429 for rejections.<br />
<ul class="mycode_list"><li>The first request returned <span style="font-weight: bold;" class="mycode_b">200</span> with the expected text.<br />
</li>
<li>An immediate second request returned <span style="font-weight: bold;" class="mycode_b">429</span>, with a matching limiter rejection in the error log.<br />
</li>
<li>After a <span style="font-weight: bold;" class="mycode_b">1.2-second pause</span>, the next request returned <span style="font-weight: bold;" class="mycode_b">200</span> with the same expected text.<br />
</li>
</ul>
The limiter stayed active throughout. Only three requests were measured in this corrected run; this was not a load test or an application-capacity benchmark. An earlier fixture path had a permissions error, which was corrected separately before this comparison. Waiting cleared this synthetic rate condition; it does not prove waiting fixes every real 429.<br />
<br />
The temporary listener was stopped and its PID file and disposable text fixture were removed. The main Nginx configuration hash remained unchanged, and the normal HTTPS website returned 200 afterward.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">5. Fix the cause at the correct layer</span><ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">A client is polling too frequently:</span> reduce unnecessary refreshes, use caching where appropriate and apply bounded backoff. Honor Retry-After when supplied. Do not create a rapid retry loop after a rejection.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">An application account or API quota is reached:</span> inspect that service's quota and application logs. Changing Nginx cannot increase a third-party API allowance. Check for duplicate jobs or repeated failed requests.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">A CDN or WAF rule is rejecting legitimate traffic:</span> inspect the matching event and rule. Consider a narrow, documented adjustment for the intended endpoint after reviewing the evidence. Keep login and abuse protections in place.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Nginx sees many visitors as one address:</span> investigate how the trusted reverse proxy communicates client addresses. This is a configuration review, not a reason to trust arbitrary forwarded headers. Incorrect trust settings can let clients spoof identities.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">A valid origin rule is too strict for an application's normal behavior:</span> measure that endpoint's legitimate request pattern before choosing a scoped allowance. Avoid a blanket site-wide exemption or unlimited burst setting.<br />
</li>
</ul>
<br />
<span style="font-weight: bold;" class="mycode_b">6. Review configuration safely</span><br />
Before editing, back up the affected site's configuration and identify any included rules. Note the rule scope, key, rate and burst behavior. The test's one-request-per-second allowance was deliberately restrictive and is not a recommended value for your site.<br />
<br />
If you have evidence for a change, alter only the intended rule, validate the complete Nginx configuration using the panel's validation mechanism and apply it only after validation passes. Keep the previous configuration for rollback. Do not add duplicate location blocks or globally disable a security plugin to fix one URL.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">7. Verify without creating more traffic problems</span><br />
Repeat a small number of safe requests at a normal pace. Check both response content and status, then inspect the corresponding logs. Confirm that ordinary users can finish the intended action and that the relevant protection still applies to excessive requests in a controlled test environment.<br />
<br />
Do not run bursts against a production login or somebody else's API. If many visitors share a corporate or mobile network, include that scenario in your review. Observe a representative period before calling an intermittent issue resolved.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">When asking for help, include</span><br />
The failing path without private query data, time and timezone, which layer logged the rejection, a short redacted log excerpt, approximate request frequency and any recent rule changes. Never include authentication headers, full session cookies or API keys.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Related guides</span><br />
<a href="https://adminsforum.com/Thread-guide-check-website-logs-in-aapanel-to-troubleshoot-site-errors" target="_blank" rel="noopener" class="mycode_url">Find website access and error logs in aaPanel</a><br />
<a href="https://adminsforum.com/Thread-guide-monitor-server-resources-during-aapanel-website-traffic-tests" target="_blank" rel="noopener" class="mycode_url">Monitor resource use during controlled traffic tests</a><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Technical reference</span><br />
<a href="https://nginx.org/en/docs/http/ngx_http_limit_req_module.html" target="_blank" rel="noopener" class="mycode_url">Official Nginx request-limit module documentation</a>]]></description>
			<content:encoded><![CDATA[<span style="font-weight: bold;" class="mycode_b">A 429 Too Many Requests response usually means a request limit has been reached. Find the layer issuing the response before changing a setting: it may be your CDN, Nginx, a security plugin or the application itself.</span><br />
<br />
This guide demonstrates one Nginx limiter on a private lab endpoint. It does not assume every aaPanel website has that rule, and it does not recommend turning off protection to make an error disappear.<br />
<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://adminsforum.com/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=323" target="_blank" title="">nginx-429-20261015.jpg</a> (Size: 116.42 KB / Downloads: 4)
<!-- end: postbit_attachments_attachment --><br />
<span style="font-style: italic;" class="mycode_i">Original AdminsForum lab results, formatted as a transcript rather than a screenshot.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">1. Capture one safe example</span><br />
Record the URL path, time and timezone, action, response status and how often the client is sending requests. Does it affect everyone, one network, one account or only a particular API? A page can send many background requests even when a user clicks once.<br />
<br />
Inspect response headers in your browser's Network tab. If a Retry-After header is present, use its stated delay; do not assume every limiter supplies it. Avoid repeatedly submitting login forms, purchases or imports while diagnosing the problem. Save a redacted error example without session cookies, authorization headers or private query strings.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">2. Match the response to website logs</span><br />
In aaPanel open <span style="font-weight: bold;" class="mycode_b">Website → PHP Project</span>, find the affected site and select <span style="font-weight: bold;" class="mycode_b">Log</span>. Check <span style="font-weight: bold;" class="mycode_b">Access log</span> and <span style="font-weight: bold;" class="mycode_b">Error log</span> around the recorded time. Refresh and expand the displayed line count if needed. Labels may vary by panel version.<br />
<br />
Our lab site's logs are at:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/www/wwwlogs/adminslab.com.log
/www/wwwlogs/adminslab.com.error.log</code></div></div>Use the actual path configured for your own domain. A bounded SSH check is:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>tail -n 100 /www/wwwlogs/example.com.log
tail -n 100 /www/wwwlogs/example.com.error.log</code></div></div>A 429 entry confirms that this log recorded the response, but does not by itself identify the rule. If there is no matching origin request, check the CDN or earlier proxy's events and verify you selected the correct log. Application-generated 429 responses need application logs too.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">3. Recognize an Nginx rate-limit rejection</span><br />
Our test error log contained:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>limiting requests, excess: 0.991 by zone "af429"</code></div></div>The named zone was our own test limiter. Its name is not a built-in aaPanel setting. We also added a diagnostic access-log field in the isolated configuration and saw:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>200 PASSED
429 REJECTED
200 PASSED</code></div></div>Default website logs may not contain that extra field. Do not expect to find it unless your log format includes it. Nginx can use a different rejection status; seeing 503 instead of 429 does not rule out a request limiter.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">4. What the private experiment tested</span><br />
We ran a separate Nginx configuration listening only on the lab's loopback interface. One exact path served a harmless text fixture. Its illustrative limit allowed one request per second with no extra burst allowance, and we explicitly selected 429 for rejections.<br />
<ul class="mycode_list"><li>The first request returned <span style="font-weight: bold;" class="mycode_b">200</span> with the expected text.<br />
</li>
<li>An immediate second request returned <span style="font-weight: bold;" class="mycode_b">429</span>, with a matching limiter rejection in the error log.<br />
</li>
<li>After a <span style="font-weight: bold;" class="mycode_b">1.2-second pause</span>, the next request returned <span style="font-weight: bold;" class="mycode_b">200</span> with the same expected text.<br />
</li>
</ul>
The limiter stayed active throughout. Only three requests were measured in this corrected run; this was not a load test or an application-capacity benchmark. An earlier fixture path had a permissions error, which was corrected separately before this comparison. Waiting cleared this synthetic rate condition; it does not prove waiting fixes every real 429.<br />
<br />
The temporary listener was stopped and its PID file and disposable text fixture were removed. The main Nginx configuration hash remained unchanged, and the normal HTTPS website returned 200 afterward.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">5. Fix the cause at the correct layer</span><ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">A client is polling too frequently:</span> reduce unnecessary refreshes, use caching where appropriate and apply bounded backoff. Honor Retry-After when supplied. Do not create a rapid retry loop after a rejection.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">An application account or API quota is reached:</span> inspect that service's quota and application logs. Changing Nginx cannot increase a third-party API allowance. Check for duplicate jobs or repeated failed requests.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">A CDN or WAF rule is rejecting legitimate traffic:</span> inspect the matching event and rule. Consider a narrow, documented adjustment for the intended endpoint after reviewing the evidence. Keep login and abuse protections in place.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Nginx sees many visitors as one address:</span> investigate how the trusted reverse proxy communicates client addresses. This is a configuration review, not a reason to trust arbitrary forwarded headers. Incorrect trust settings can let clients spoof identities.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">A valid origin rule is too strict for an application's normal behavior:</span> measure that endpoint's legitimate request pattern before choosing a scoped allowance. Avoid a blanket site-wide exemption or unlimited burst setting.<br />
</li>
</ul>
<br />
<span style="font-weight: bold;" class="mycode_b">6. Review configuration safely</span><br />
Before editing, back up the affected site's configuration and identify any included rules. Note the rule scope, key, rate and burst behavior. The test's one-request-per-second allowance was deliberately restrictive and is not a recommended value for your site.<br />
<br />
If you have evidence for a change, alter only the intended rule, validate the complete Nginx configuration using the panel's validation mechanism and apply it only after validation passes. Keep the previous configuration for rollback. Do not add duplicate location blocks or globally disable a security plugin to fix one URL.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">7. Verify without creating more traffic problems</span><br />
Repeat a small number of safe requests at a normal pace. Check both response content and status, then inspect the corresponding logs. Confirm that ordinary users can finish the intended action and that the relevant protection still applies to excessive requests in a controlled test environment.<br />
<br />
Do not run bursts against a production login or somebody else's API. If many visitors share a corporate or mobile network, include that scenario in your review. Observe a representative period before calling an intermittent issue resolved.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">When asking for help, include</span><br />
The failing path without private query data, time and timezone, which layer logged the rejection, a short redacted log excerpt, approximate request frequency and any recent rule changes. Never include authentication headers, full session cookies or API keys.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Related guides</span><br />
<a href="https://adminsforum.com/Thread-guide-check-website-logs-in-aapanel-to-troubleshoot-site-errors" target="_blank" rel="noopener" class="mycode_url">Find website access and error logs in aaPanel</a><br />
<a href="https://adminsforum.com/Thread-guide-monitor-server-resources-during-aapanel-website-traffic-tests" target="_blank" rel="noopener" class="mycode_url">Monitor resource use during controlled traffic tests</a><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Technical reference</span><br />
<a href="https://nginx.org/en/docs/http/ngx_http_limit_req_module.html" target="_blank" rel="noopener" class="mycode_url">Official Nginx request-limit module documentation</a>]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[Fix 504 Gateway Timeout Errors in aaPanel Using Logs]]></title>
			<link>https://adminsforum.com/Thread-guide-fix-504-gateway-timeout-errors-in-aapanel-using-logs</link>
			<pubDate>Thu, 15 Oct 2026 12:24:18 +0800</pubDate>
			<dc:creator><![CDATA[<a href="https://adminsforum.com/member.php?action=profile&uid=2">DEV</a>]]></dc:creator>
			<guid isPermaLink="false">https://adminsforum.com/Thread-guide-fix-504-gateway-timeout-errors-in-aapanel-using-logs</guid>
			<description><![CDATA[<span style="font-weight: bold;" class="mycode_b">A 504 Gateway Timeout means a gateway did not receive a timely response from its upstream. The useful next step is to identify which gateway timed out and what it was waiting for.</span> A slow SQL query, external API, busy PHP worker pool or an unreachable upstream can require very different fixes.<br />
<br />
This guide uses an isolated Nginx HTTP-proxy experiment, then shows how to investigate a real aaPanel website. The tested values below are deliberately tiny lab settings, not recommended production limits.<br />
<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://adminsforum.com/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=322" target="_blank" title="">nginx-504-20261015.jpg</a> (Size: 130.65 KB / Downloads: 4)
<!-- end: postbit_attachments_attachment --><br />
<span style="font-style: italic;" class="mycode_i">AdminsForum original lab results, presented as a formatted transcript rather than a screenshot.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">1. Record the failing request before changing settings</span><br />
Write down the exact time and timezone, domain, path, action and approximate waiting time. Does one report page fail, or every page? Does it fail only during traffic peaks? Avoid repeatedly submitting payments, imports or other actions that might continue on the server after the browser gives up.<br />
<br />
Check whether the response comes from your origin Nginx, another reverse proxy or a CDN. A branded error page is a clue, not conclusive proof of which layer caused the delay. Match the request against origin logs. Do not disable your firewall or expose a private origin to diagnose this.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">2. Find the matching website logs in aaPanel</span><br />
Open <span style="font-weight: bold;" class="mycode_b">Website → PHP Project</span>, locate the affected site and use its <span style="font-weight: bold;" class="mycode_b">Log</span> control. Inspect <span style="font-weight: bold;" class="mycode_b">Access log</span> and <span style="font-weight: bold;" class="mycode_b">Error log</span> around the recorded time. Refresh the viewer and expand the displayed line count if necessary. Panel layouts differ by version.<br />
<br />
On our lab site the files are:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/www/wwwlogs/adminslab.com.log
/www/wwwlogs/adminslab.com.error.log</code></div></div>Replace the domain with your own site's actual configured log path. A bounded SSH check is:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>tail -n 100 /www/wwwlogs/example.com.error.log
tail -n 100 /www/wwwlogs/example.com.log</code></div></div>Do not paste complete logs publicly: redact tokens, private query strings, addresses and customer information. A browser 504 with no matching origin request may point to an earlier proxy layer or the wrong log file.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">3. Read the upstream error, not just the status code</span><br />
Our failing request produced this shortened error:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>upstream timed out (110: Connection timed out)
while reading response header from upstream</code></div></div>Here Nginx was waiting for the upstream response header. A message about connecting to the upstream points to a different phase: check the upstream address, listener, route and service health. A connection refusal often leads to 502 instead; do not treat every gateway failure as the same problem.<br />
<br />
The error line's upstream field helps distinguish HTTP proxying from FastCGI. Correlate the path and timestamp with the application log and, for PHP, its slow log. A 504 alone does not prove that MySQL, PHP or Nginx needs a larger limit.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">4. Understand the two common read-timeout settings</span><br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code># HTTP reverse proxy: Node, Python or another HTTP service
proxy_read_timeout 30s;

# FastCGI: commonly a PHP-FPM website
fastcgi_read_timeout 30s;</code></div></div>These are illustrative directives, not instructions to add both. Identify the active location and included configuration first. Do not create a second conflicting location block or edit a shared PHP include without understanding every affected site.<br />
<br />
Nginx read timeouts measure the allowed gap between successive reads from the upstream; they are not a universal total request duration limit. Connection timeouts govern another phase. PHP execution limits, PHP-FPM termination limits and CDN or load-balancer limits are separate and may end the request sooner.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">5. What our isolated test actually demonstrated</span><br />
The lab used the installed Nginx 1.30.5 binary, a separate configuration and two loopback-only listeners. Its dummy HTTP upstream deliberately waited before returning a response. There were only three measured requests:<br />
<ul class="mycode_list"><li>Delay 2 seconds, read timeout 1 second: <span style="font-weight: bold;" class="mycode_b">504 in 1.002187 seconds</span>, with an upstream-header timeout in the error log.<br />
</li>
<li>Same 2-second delay, read timeout 4 seconds: <span style="font-weight: bold;" class="mycode_b">200 in 2.002012 seconds</span>. The longer allowance did not make the upstream faster.<br />
</li>
<li>Artificial delay reduced to 0.2 seconds, original 1-second timeout restored: <span style="font-weight: bold;" class="mycode_b">200 in 0.202015 seconds</span>.<br />
</li>
</ul>
The last phase changed a synthetic sleep, not a real database query or production application. This demonstrates timeout behavior, not PHP capacity or a universal performance improvement. Both test listeners were closed, the isolated PID file disappeared, the main Nginx configuration hash stayed unchanged and the normal HTTPS site returned 200 afterward.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">6. Investigate what the real application is waiting for</span><ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">Slow PHP code:</span> correlate a PHP-FPM slow-log stack with the failing URL. Check whether workers are busy or requests are queuing. Raising worker counts beyond available memory can make the situation worse.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Database work:</span> identify the actual slow query and inspect its plan before adding an index. Check locks and transaction duration. Do not change global database limits based on one gateway error.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">External API:</span> inspect the application's outgoing request timeout and upstream provider status. Use bounded retries and a clear failure response; retries can multiply load.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Traffic or resource pressure:</span> compare CPU, available memory, swap activity and disk latency with the incident time. A quiet server after the event does not rule out an earlier spike.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Long exports or imports:</span> consider a background job with status polling and a downloadable result. Increasing web timeouts indefinitely leaves connections and workers occupied.<br />
</li>
</ul>
<br />
<span style="font-weight: bold;" class="mycode_b">7. If a timeout adjustment is justified, make it deliberately</span><br />
Back up the affected site's configuration. Record the old value, the expected operation duration and the limits of every proxy in front of it. In aaPanel open the affected site's configuration and locate the active proxy or FastCGI section. Change only the appropriate directive in the intended scope, then run the panel's configuration validation before applying or reloading it. Restore the backup if validation fails.<br />
<br />
Do not copy the lab's 1-second or 4-second settings to your website. Choose a finite budget based on a legitimate request and measured behavior. A larger budget is sometimes appropriate, but it also permits longer resource occupancy and does not repair slow application work.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">8. Verify the result and keep a rollback path</span><br />
Repeat one safe request and compare status, response content and elapsed time. Check the application actually finished; an HTTP 200 alone can contain an application error. Confirm the error log has no new matching upstream timeout, inspect nearby normal pages and monitor resource use. For an intermittent issue, observe a representative traffic period before declaring it fixed.<br />
<br />
A simple non-mutating request timing example is:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>curl -sS --max-time 45 -o /dev/null &#92;
  -w 'HTTP %{http_code} | total %{time_total}s&#92;n' &#92;
  https://example.com/safe-status-page</code></div></div>Use a real, safe URL on your site. curl's own maximum time is a client-side limit, separate from Nginx's upstream timeout. Never include login tokens or secrets in a public command example.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Related troubleshooting guides</span><br />
<a href="https://adminsforum.com/Thread-guide-check-website-logs-in-aapanel-to-troubleshoot-site-errors" target="_blank" rel="noopener" class="mycode_url">Find website access and error logs in aaPanel</a><br />
<a href="https://adminsforum.com/Thread-guide-find-slow-php-requests-in-aapanel-with-php-fpm-slow-logs" target="_blank" rel="noopener" class="mycode_url">Trace slow PHP requests with PHP-FPM slow logs</a><br />
<a href="https://adminsforum.com/Thread-guide-tune-mariadb-queries-in-aapanel-with-explain-and-index-tests" target="_blank" rel="noopener" class="mycode_url">Investigate MariaDB query plans and indexes</a><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Technical references</span><br />
<a href="https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_read_timeout" target="_blank" rel="noopener" class="mycode_url">Nginx proxy_read_timeout documentation</a><br />
<a href="https://nginx.org/en/docs/http/ngx_http_fastcgi_module.html#fastcgi_read_timeout" target="_blank" rel="noopener" class="mycode_url">Nginx fastcgi_read_timeout documentation</a>]]></description>
			<content:encoded><![CDATA[<span style="font-weight: bold;" class="mycode_b">A 504 Gateway Timeout means a gateway did not receive a timely response from its upstream. The useful next step is to identify which gateway timed out and what it was waiting for.</span> A slow SQL query, external API, busy PHP worker pool or an unreachable upstream can require very different fixes.<br />
<br />
This guide uses an isolated Nginx HTTP-proxy experiment, then shows how to investigate a real aaPanel website. The tested values below are deliberately tiny lab settings, not recommended production limits.<br />
<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://adminsforum.com/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=322" target="_blank" title="">nginx-504-20261015.jpg</a> (Size: 130.65 KB / Downloads: 4)
<!-- end: postbit_attachments_attachment --><br />
<span style="font-style: italic;" class="mycode_i">AdminsForum original lab results, presented as a formatted transcript rather than a screenshot.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">1. Record the failing request before changing settings</span><br />
Write down the exact time and timezone, domain, path, action and approximate waiting time. Does one report page fail, or every page? Does it fail only during traffic peaks? Avoid repeatedly submitting payments, imports or other actions that might continue on the server after the browser gives up.<br />
<br />
Check whether the response comes from your origin Nginx, another reverse proxy or a CDN. A branded error page is a clue, not conclusive proof of which layer caused the delay. Match the request against origin logs. Do not disable your firewall or expose a private origin to diagnose this.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">2. Find the matching website logs in aaPanel</span><br />
Open <span style="font-weight: bold;" class="mycode_b">Website → PHP Project</span>, locate the affected site and use its <span style="font-weight: bold;" class="mycode_b">Log</span> control. Inspect <span style="font-weight: bold;" class="mycode_b">Access log</span> and <span style="font-weight: bold;" class="mycode_b">Error log</span> around the recorded time. Refresh the viewer and expand the displayed line count if necessary. Panel layouts differ by version.<br />
<br />
On our lab site the files are:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/www/wwwlogs/adminslab.com.log
/www/wwwlogs/adminslab.com.error.log</code></div></div>Replace the domain with your own site's actual configured log path. A bounded SSH check is:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>tail -n 100 /www/wwwlogs/example.com.error.log
tail -n 100 /www/wwwlogs/example.com.log</code></div></div>Do not paste complete logs publicly: redact tokens, private query strings, addresses and customer information. A browser 504 with no matching origin request may point to an earlier proxy layer or the wrong log file.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">3. Read the upstream error, not just the status code</span><br />
Our failing request produced this shortened error:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>upstream timed out (110: Connection timed out)
while reading response header from upstream</code></div></div>Here Nginx was waiting for the upstream response header. A message about connecting to the upstream points to a different phase: check the upstream address, listener, route and service health. A connection refusal often leads to 502 instead; do not treat every gateway failure as the same problem.<br />
<br />
The error line's upstream field helps distinguish HTTP proxying from FastCGI. Correlate the path and timestamp with the application log and, for PHP, its slow log. A 504 alone does not prove that MySQL, PHP or Nginx needs a larger limit.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">4. Understand the two common read-timeout settings</span><br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code># HTTP reverse proxy: Node, Python or another HTTP service
proxy_read_timeout 30s;

# FastCGI: commonly a PHP-FPM website
fastcgi_read_timeout 30s;</code></div></div>These are illustrative directives, not instructions to add both. Identify the active location and included configuration first. Do not create a second conflicting location block or edit a shared PHP include without understanding every affected site.<br />
<br />
Nginx read timeouts measure the allowed gap between successive reads from the upstream; they are not a universal total request duration limit. Connection timeouts govern another phase. PHP execution limits, PHP-FPM termination limits and CDN or load-balancer limits are separate and may end the request sooner.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">5. What our isolated test actually demonstrated</span><br />
The lab used the installed Nginx 1.30.5 binary, a separate configuration and two loopback-only listeners. Its dummy HTTP upstream deliberately waited before returning a response. There were only three measured requests:<br />
<ul class="mycode_list"><li>Delay 2 seconds, read timeout 1 second: <span style="font-weight: bold;" class="mycode_b">504 in 1.002187 seconds</span>, with an upstream-header timeout in the error log.<br />
</li>
<li>Same 2-second delay, read timeout 4 seconds: <span style="font-weight: bold;" class="mycode_b">200 in 2.002012 seconds</span>. The longer allowance did not make the upstream faster.<br />
</li>
<li>Artificial delay reduced to 0.2 seconds, original 1-second timeout restored: <span style="font-weight: bold;" class="mycode_b">200 in 0.202015 seconds</span>.<br />
</li>
</ul>
The last phase changed a synthetic sleep, not a real database query or production application. This demonstrates timeout behavior, not PHP capacity or a universal performance improvement. Both test listeners were closed, the isolated PID file disappeared, the main Nginx configuration hash stayed unchanged and the normal HTTPS site returned 200 afterward.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">6. Investigate what the real application is waiting for</span><ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">Slow PHP code:</span> correlate a PHP-FPM slow-log stack with the failing URL. Check whether workers are busy or requests are queuing. Raising worker counts beyond available memory can make the situation worse.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Database work:</span> identify the actual slow query and inspect its plan before adding an index. Check locks and transaction duration. Do not change global database limits based on one gateway error.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">External API:</span> inspect the application's outgoing request timeout and upstream provider status. Use bounded retries and a clear failure response; retries can multiply load.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Traffic or resource pressure:</span> compare CPU, available memory, swap activity and disk latency with the incident time. A quiet server after the event does not rule out an earlier spike.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Long exports or imports:</span> consider a background job with status polling and a downloadable result. Increasing web timeouts indefinitely leaves connections and workers occupied.<br />
</li>
</ul>
<br />
<span style="font-weight: bold;" class="mycode_b">7. If a timeout adjustment is justified, make it deliberately</span><br />
Back up the affected site's configuration. Record the old value, the expected operation duration and the limits of every proxy in front of it. In aaPanel open the affected site's configuration and locate the active proxy or FastCGI section. Change only the appropriate directive in the intended scope, then run the panel's configuration validation before applying or reloading it. Restore the backup if validation fails.<br />
<br />
Do not copy the lab's 1-second or 4-second settings to your website. Choose a finite budget based on a legitimate request and measured behavior. A larger budget is sometimes appropriate, but it also permits longer resource occupancy and does not repair slow application work.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">8. Verify the result and keep a rollback path</span><br />
Repeat one safe request and compare status, response content and elapsed time. Check the application actually finished; an HTTP 200 alone can contain an application error. Confirm the error log has no new matching upstream timeout, inspect nearby normal pages and monitor resource use. For an intermittent issue, observe a representative traffic period before declaring it fixed.<br />
<br />
A simple non-mutating request timing example is:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>curl -sS --max-time 45 -o /dev/null &#92;
  -w 'HTTP %{http_code} | total %{time_total}s&#92;n' &#92;
  https://example.com/safe-status-page</code></div></div>Use a real, safe URL on your site. curl's own maximum time is a client-side limit, separate from Nginx's upstream timeout. Never include login tokens or secrets in a public command example.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Related troubleshooting guides</span><br />
<a href="https://adminsforum.com/Thread-guide-check-website-logs-in-aapanel-to-troubleshoot-site-errors" target="_blank" rel="noopener" class="mycode_url">Find website access and error logs in aaPanel</a><br />
<a href="https://adminsforum.com/Thread-guide-find-slow-php-requests-in-aapanel-with-php-fpm-slow-logs" target="_blank" rel="noopener" class="mycode_url">Trace slow PHP requests with PHP-FPM slow logs</a><br />
<a href="https://adminsforum.com/Thread-guide-tune-mariadb-queries-in-aapanel-with-explain-and-index-tests" target="_blank" rel="noopener" class="mycode_url">Investigate MariaDB query plans and indexes</a><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Technical references</span><br />
<a href="https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_read_timeout" target="_blank" rel="noopener" class="mycode_url">Nginx proxy_read_timeout documentation</a><br />
<a href="https://nginx.org/en/docs/http/ngx_http_fastcgi_module.html#fastcgi_read_timeout" target="_blank" rel="noopener" class="mycode_url">Nginx fastcgi_read_timeout documentation</a>]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[Fix 413 Request Entity Too Large Errors in aaPanel]]></title>
			<link>https://adminsforum.com/Thread-guide-fix-413-request-entity-too-large-errors-in-aapanel</link>
			<pubDate>Thu, 15 Oct 2026 11:34:38 +0800</pubDate>
			<dc:creator><![CDATA[<a href="https://adminsforum.com/member.php?action=profile&uid=2">DEV</a>]]></dc:creator>
			<guid isPermaLink="false">https://adminsforum.com/Thread-guide-fix-413-request-entity-too-large-errors-in-aapanel</guid>
			<description><![CDATA[<span style="font-weight: bold;" class="mycode_b">A 413 “Request Entity Too Large” response can come from a request-size limit before PHP handles an upload. Identify the layer returning it, check the matching log and change the smallest relevant limit. Increasing PHP's upload setting alone cannot fix a request rejected by Nginx.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Tested</span>: Ubuntu 22.04.5 and the aaPanel lab's installed Nginx 1.30.5 binary. We ran a separate temporary Nginx instance listening only on 127.0.0.1, with its own configuration, PID and logs. A 2KiB request failed under a 1KiB limit and reached a dummy 200 handler under a 4KiB limit. We shut down the test instance afterwards. The normal website configuration and PHP settings were not changed.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">1. Record the failed operation</span><br />
Note the hostname/path, time/timezone, method, approximate file size, total request size and HTTP status. Find out whether a small upload works and a larger one fails. A multipart request contains framing and other fields in addition to the file bytes.<br />
Check the browser's failed network request and the response. The wording may vary by Nginx version or another proxy. A CDN, reverse proxy, application or upload service can impose a separate limit; a response logo alone is not proof of the failing layer.<br />
Do not repeatedly submit production imports or other actions that might create duplicate records. Use a controlled test workflow and harmless input.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">2. Match the Nginx access and error logs</span><br />
In the tested aaPanel interface, open Website → PHP Project → the site's Log button → Access log / Error log. Confirm the hostname and source path. Read <a href="https://adminsforum.com/Thread-guide-check-website-logs-in-aapanel-to-troubleshoot-site-errors" target="_blank" rel="noopener" class="mycode_url">the website-log guide</a> for detailed searching and time matching.<br />
Our isolated test access log recorded POST /request-size-check with status 413. Its matching error said:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>client intended to send too large body: 2048 bytes</code></div></div>This supported a Nginx request-size diagnosis. It was not evidence of a PHP memory error, missing extension or failed database connection. If your website has no matching request, investigate the correct host/log and any earlier proxy layer before editing PHP.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">3. Understand the tiny lab comparison</span><br />
We sent the same 2,048-byte raw request body in both phases. With the relevant location set to:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>client_max_body_size 1k;</code></div></div>Nginx returned 413. We changed only that test limit to:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>client_max_body_size 4k;</code></div></div>After validation and a reload of the isolated instance, the same request received 200 and the dummy response text. The access log recorded both outcomes.<br />
<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://adminsforum.com/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=321" target="_blank" title="">nginx-413-20261015.jpg</a> (Size: 124.89 KB / Downloads: 6)
<!-- end: postbit_attachments_attachment --><br />
<span style="font-style: italic;" class="mycode_i">AdminsForum formatted transcript of the original experiment, not a terminal screenshot.</span><br />
<br />
The handler returned a fixed message. This demonstrates the Nginx size gate; it does not prove PHP parsed multipart data, an application validated a file or anything was stored successfully. The endpoint was private, no public firewall port was opened and the normal website remained available.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">4. Inspect the effective site configuration</span><br />
For a real aaPanel incident, locate the affected website under Website → PHP Project and inspect its Conf/site configuration. Labels vary by release. Back up the relevant file before changing it and confirm which server/location handles the failing URL.<br />
The Nginx directive can appear in http, server or location context. Check inherited settings and a more specific location rather than assuming a global value controls every route. Avoid adding duplicate directives in the same context or replacing an existing PHP handler with an incomplete example location.<br />
An administrator can inspect the effective configuration privately with the installed binary:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/www/server/nginx/sbin/nginx -T</code></div></div>The output can contain private configuration details. Do not upload the entire dump to a public thread.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">5. Choose a finite limit for the actual workflow</span><br />
An illustrative site-level value is:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>client_max_body_size 80m;</code></div></div>This is an example, not a value applied to the existing lab website or a universal recommendation. Place a chosen limit in the appropriate existing context and consider legitimate request size, concurrent uploads, temporary storage and application policy. Setting the Nginx value to zero disables its body-size check; it was not needed for our test.<br />
Keep limits purposeful instead of making every upload unlimited. A narrow policy can be appropriate for a particular route, but it must preserve the route's existing processing/authentication configuration.<br />
If a CDN or another reverse proxy rejects the request earlier, an origin-only change cannot remove that layer's limit. Review the actual service's documented current limits and supported workflow rather than weakening unrelated security controls.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">6. Align PHP and application limits separately</span><br />
When the request reaches PHP, check the site's selected PHP version and its active FPM settings. Relevant settings include upload_max_filesize for an uploaded file and post_max_size for the total POST data. Allow for request overhead and multiple files according to the intended application behavior.<br />
Exceeding PHP's POST limit can leave POST/file data empty, so the application's error may differ from a Nginx 413. Verify the effective serving runtime; a CLI setting may not describe the website's PHP-FPM pool.<br />
Follow <a href="https://adminsforum.com/Thread-guide-change-php-upload-and-memory-limits-in-aapanel" target="_blank" rel="noopener" class="mycode_url">Change PHP Upload and Memory Limits</a> and <a href="https://adminsforum.com/Thread-guide-increase-wordpress-php-upload-limits-in-aapanel-to-64-mb" target="_blank" rel="noopener" class="mycode_url">the tested WordPress 64MB upload guide</a> for those existing workflows. Application/plugin limits, validation and available temporary storage still matter. We did not change PHP limits in this experiment.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">7. Validate and verify a real repair</span><br />
Before reloading the affected site configuration on a normal aaPanel-managed Nginx installation:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/www/server/nginx/sbin/nginx -t</code></div></div>Use the panel's supported reload control only after validation succeeds. Our experiment reloaded only its separately configured test process, so the existing website configuration was not involved.<br />
Test a small permitted upload and the intended larger upload through the real application, inspect the response/content and confirm the expected file or record was created. Check fresh logs and a normal page. Do not treat a dummy HTTP 200 as proof that a production upload completed.<br />
Preserve a rollback copy. If a change breaks another route or causes resource pressure, restore the previous configuration, validate and repeat the relevant health checks.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">8. Cleanup and test limits</span><br />
The temporary listener was stopped, its PID file was removed by shutdown and a connection check confirmed its port was closed. The main Nginx configuration hash was unchanged and the normal website health check returned 200.<br />
We retained the private experiment configuration and result logs as documentation. No real uploaded user file, production upload cap, PHP pool or database was modified. The tiny request was chosen to demonstrate the mechanism with bounded work rather than sending large files at a busy server.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">References</span>: <a href="https://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size" target="_blank" rel="noopener" class="mycode_url">Nginx client_max_body_size</a> and <a href="https://www.php.net/manual/en/ini.core.php#ini.post-max-size" target="_blank" rel="noopener" class="mycode_url">PHP POST-size configuration</a>.]]></description>
			<content:encoded><![CDATA[<span style="font-weight: bold;" class="mycode_b">A 413 “Request Entity Too Large” response can come from a request-size limit before PHP handles an upload. Identify the layer returning it, check the matching log and change the smallest relevant limit. Increasing PHP's upload setting alone cannot fix a request rejected by Nginx.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Tested</span>: Ubuntu 22.04.5 and the aaPanel lab's installed Nginx 1.30.5 binary. We ran a separate temporary Nginx instance listening only on 127.0.0.1, with its own configuration, PID and logs. A 2KiB request failed under a 1KiB limit and reached a dummy 200 handler under a 4KiB limit. We shut down the test instance afterwards. The normal website configuration and PHP settings were not changed.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">1. Record the failed operation</span><br />
Note the hostname/path, time/timezone, method, approximate file size, total request size and HTTP status. Find out whether a small upload works and a larger one fails. A multipart request contains framing and other fields in addition to the file bytes.<br />
Check the browser's failed network request and the response. The wording may vary by Nginx version or another proxy. A CDN, reverse proxy, application or upload service can impose a separate limit; a response logo alone is not proof of the failing layer.<br />
Do not repeatedly submit production imports or other actions that might create duplicate records. Use a controlled test workflow and harmless input.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">2. Match the Nginx access and error logs</span><br />
In the tested aaPanel interface, open Website → PHP Project → the site's Log button → Access log / Error log. Confirm the hostname and source path. Read <a href="https://adminsforum.com/Thread-guide-check-website-logs-in-aapanel-to-troubleshoot-site-errors" target="_blank" rel="noopener" class="mycode_url">the website-log guide</a> for detailed searching and time matching.<br />
Our isolated test access log recorded POST /request-size-check with status 413. Its matching error said:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>client intended to send too large body: 2048 bytes</code></div></div>This supported a Nginx request-size diagnosis. It was not evidence of a PHP memory error, missing extension or failed database connection. If your website has no matching request, investigate the correct host/log and any earlier proxy layer before editing PHP.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">3. Understand the tiny lab comparison</span><br />
We sent the same 2,048-byte raw request body in both phases. With the relevant location set to:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>client_max_body_size 1k;</code></div></div>Nginx returned 413. We changed only that test limit to:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>client_max_body_size 4k;</code></div></div>After validation and a reload of the isolated instance, the same request received 200 and the dummy response text. The access log recorded both outcomes.<br />
<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://adminsforum.com/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=321" target="_blank" title="">nginx-413-20261015.jpg</a> (Size: 124.89 KB / Downloads: 6)
<!-- end: postbit_attachments_attachment --><br />
<span style="font-style: italic;" class="mycode_i">AdminsForum formatted transcript of the original experiment, not a terminal screenshot.</span><br />
<br />
The handler returned a fixed message. This demonstrates the Nginx size gate; it does not prove PHP parsed multipart data, an application validated a file or anything was stored successfully. The endpoint was private, no public firewall port was opened and the normal website remained available.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">4. Inspect the effective site configuration</span><br />
For a real aaPanel incident, locate the affected website under Website → PHP Project and inspect its Conf/site configuration. Labels vary by release. Back up the relevant file before changing it and confirm which server/location handles the failing URL.<br />
The Nginx directive can appear in http, server or location context. Check inherited settings and a more specific location rather than assuming a global value controls every route. Avoid adding duplicate directives in the same context or replacing an existing PHP handler with an incomplete example location.<br />
An administrator can inspect the effective configuration privately with the installed binary:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/www/server/nginx/sbin/nginx -T</code></div></div>The output can contain private configuration details. Do not upload the entire dump to a public thread.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">5. Choose a finite limit for the actual workflow</span><br />
An illustrative site-level value is:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>client_max_body_size 80m;</code></div></div>This is an example, not a value applied to the existing lab website or a universal recommendation. Place a chosen limit in the appropriate existing context and consider legitimate request size, concurrent uploads, temporary storage and application policy. Setting the Nginx value to zero disables its body-size check; it was not needed for our test.<br />
Keep limits purposeful instead of making every upload unlimited. A narrow policy can be appropriate for a particular route, but it must preserve the route's existing processing/authentication configuration.<br />
If a CDN or another reverse proxy rejects the request earlier, an origin-only change cannot remove that layer's limit. Review the actual service's documented current limits and supported workflow rather than weakening unrelated security controls.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">6. Align PHP and application limits separately</span><br />
When the request reaches PHP, check the site's selected PHP version and its active FPM settings. Relevant settings include upload_max_filesize for an uploaded file and post_max_size for the total POST data. Allow for request overhead and multiple files according to the intended application behavior.<br />
Exceeding PHP's POST limit can leave POST/file data empty, so the application's error may differ from a Nginx 413. Verify the effective serving runtime; a CLI setting may not describe the website's PHP-FPM pool.<br />
Follow <a href="https://adminsforum.com/Thread-guide-change-php-upload-and-memory-limits-in-aapanel" target="_blank" rel="noopener" class="mycode_url">Change PHP Upload and Memory Limits</a> and <a href="https://adminsforum.com/Thread-guide-increase-wordpress-php-upload-limits-in-aapanel-to-64-mb" target="_blank" rel="noopener" class="mycode_url">the tested WordPress 64MB upload guide</a> for those existing workflows. Application/plugin limits, validation and available temporary storage still matter. We did not change PHP limits in this experiment.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">7. Validate and verify a real repair</span><br />
Before reloading the affected site configuration on a normal aaPanel-managed Nginx installation:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/www/server/nginx/sbin/nginx -t</code></div></div>Use the panel's supported reload control only after validation succeeds. Our experiment reloaded only its separately configured test process, so the existing website configuration was not involved.<br />
Test a small permitted upload and the intended larger upload through the real application, inspect the response/content and confirm the expected file or record was created. Check fresh logs and a normal page. Do not treat a dummy HTTP 200 as proof that a production upload completed.<br />
Preserve a rollback copy. If a change breaks another route or causes resource pressure, restore the previous configuration, validate and repeat the relevant health checks.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">8. Cleanup and test limits</span><br />
The temporary listener was stopped, its PID file was removed by shutdown and a connection check confirmed its port was closed. The main Nginx configuration hash was unchanged and the normal website health check returned 200.<br />
We retained the private experiment configuration and result logs as documentation. No real uploaded user file, production upload cap, PHP pool or database was modified. The tiny request was chosen to demonstrate the mechanism with bounded work rather than sending large files at a busy server.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">References</span>: <a href="https://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size" target="_blank" rel="noopener" class="mycode_url">Nginx client_max_body_size</a> and <a href="https://www.php.net/manual/en/ini.core.php#ini.post-max-size" target="_blank" rel="noopener" class="mycode_url">PHP POST-size configuration</a>.]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[Fix 403 Forbidden Errors in aaPanel Using Logs]]></title>
			<link>https://adminsforum.com/Thread-guide-fix-403-forbidden-errors-in-aapanel-using-logs</link>
			<pubDate>Wed, 14 Oct 2026 21:18:55 +0800</pubDate>
			<dc:creator><![CDATA[<a href="https://adminsforum.com/member.php?action=profile&uid=2">DEV</a>]]></dc:creator>
			<guid isPermaLink="false">https://adminsforum.com/Thread-guide-fix-403-forbidden-errors-in-aapanel-using-logs</guid>
			<description><![CDATA[<span style="font-weight: bold;" class="mycode_b">A 403 Forbidden response means the request was denied, but it does not identify the rule or permission responsible. Read the matching error before changing file ownership, enabling directory listings or disabling security controls.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Tested</span>: aaPanel Free 8.0.7, Ubuntu 22.04.5 and Nginx 1.30.5. We created one temporary public-text fixture, withheld file access to reproduce a permission-related 403, then permitted group read on that file and verified HTTP 200. It was removed afterwards. No application directory, access rule or global service setting was changed.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">1. Identify the request and the layer denying it</span><br />
Record hostname/path, time/timezone, status, request method, client context and recent changes. Check whether one file, a directory URL, all website pages or only an authenticated action is denied.<br />
Cloudflare/WAF, Nginx and the application can each deny a request. A status or response logo is a clue, not proof of the source. A browser login failure may need account/application evidence rather than filesystem permissions.<br />
For a harmless GET you control:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>curl -sS --max-time 15 -D - -o /dev/null https://example.com/affected-path</code></div></div>Replace the hostname/path; use curl.exe and NUL on Windows. Keep certificate validation enabled. Do not test protected data by bypassing an access policy merely to get a successful response.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">2. Match the website's access and error logs</span><br />
In our aaPanel interface: Website → PHP Project → the exact site's Log button → Access log / Error log. Confirm the source path and use Refresh. Read <a href="https://adminsforum.com/Thread-guide-check-website-logs-in-aapanel-to-troubleshoot-site-errors" target="_blank" rel="noopener" class="mycode_url">the website log guide</a> for detailed searching and timestamp checks.<br />
Our test request appeared as 403 in the access log. The corresponding Nginx error named the temporary text file and reported:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>open() ... failed (13: Permission denied)</code></div></div>This supported a filesystem-access diagnosis for that static file. It was different from a missing file (404) or an undefined PHP function (500).<br />
<br />
<span style="font-weight: bold;" class="mycode_b">3. Inspect the exact file and its parent directories</span><br />
Before a repair, record permissions, owner and group:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>ls -l /path/to/affected-file
namei -l /path/to/affected-file</code></div></div>Use the actual path from the matching error. namei displays path components so you can inspect parent-directory traversal as well as the final file. Availability depends on your Linux tools.<br />
A server needs appropriate read access to a served static file and search/traverse access to its parent directories. An unreadable file and a non-traversable parent require different targeted changes. ACLs and platform security controls can also affect access; mode bits alone do not explain every denial.<br />
Our fixture's parents were traversable, and the denied file had mode 000 with owner root and group www. No parent permissions were changed during the test.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">4. Confirm the process that needs access</span><br />
Our Nginx master ran as root, but its workers ran as www:www. The worker identity is the relevant one for this static request; an administrator being able to read the file does not prove the web worker can.<br />
A useful check that avoids full process arguments is:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>ps -eo user,group,comm</code></div></div>Inspect the Nginx rows. Do not assume that every panel uses www: different services and PHP pools can run under different users. Ownership should follow your deployment model, not a blanket command copied from another server.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">5. Apply the smallest justified permission change</span><br />
For our existing fixture ownership root:www, mode 640 gave the owner read/write, the group read and others no access. Because the workers used group www, this permitted the intended static response without allowing them to write to the file.<br />
Our one-file repair was equivalent to:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>chmod 640 /www/wwwroot/adminslab.com/af-http403-lab-20261014.txt</code></div></div>That path was our disposable text fixture. Before adapting the command, verify the intended owner/group and runtime identity, record the original mode and select only the affected file. It does not change ownership or grant a different group access.<br />
This was a file-specific lab example, not a universal website permission preset. Other deployments may use another group or access model. A directory needs traversal permissions, so do not apply file mode 640 recursively to directories.<br />
The existing guide <a href="https://adminsforum.com/Thread-guide-set-file-permissions-and-ownership-in-aapanel" target="_blank" rel="noopener" class="mycode_url">Set File Permissions and Ownership in aaPanel</a> covers the panel controls. Preserve the original mode/owner/group so a real repair can be reversed. Do not recursively apply chmod 777 or give a runtime write access to all application code just to eliminate a 403.<br />
<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://adminsforum.com/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=319" target="_blank" title="">http403-permission-20261014.jpg</a> (Size: 131.87 KB / Downloads: 7)
<!-- end: postbit_attachments_attachment --><br />
<span style="font-style: italic;" class="mycode_i">AdminsForum formatted transcript of the original experiment, not a panel screenshot.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">6. Verify the response and new log evidence</span><br />
After the targeted file change, the same URL returned HTTP 200 with the expected text. The access log recorded 200, no new error entry matched that file, and the normal homepage also returned 200.<br />
No Nginx reload or restart was needed for this filesystem-mode change. If you instead edit a server configuration, validate it and use the supported reload procedure before testing its behavior.<br />
We removed the temporary fixture after verification. The deliberately inaccessible file should not be left as a public test URL. Existing application files and permissions remained untouched.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">7. Distinguish other 403 causes</span><br />
The following are investigation examples, not failures induced in this test:<ul class="mycode_list"><li>Directory URL denied: inspect the intended default document, running/document root and directory-index configuration. A missing expected index is not a reason to expose a directory listing of website files.<br />
</li>
<li>Access-rule denial: inspect the matching Nginx location and ordered allow/deny rules, or the applicable panel access restriction. Confirm whether the denial is intentional before changing it.<br />
</li>
<li>WAF/CDN denial: inspect the corresponding security event and request context. Resolve a verified false positive narrowly; do not disable the whole firewall or WAF as a shortcut.<br />
</li>
<li>Application denial: inspect account role, authentication/session state, CSRF checks and the application logger. A filesystem chmod will not correct an authorization decision.<br />
</li>
<li>Permission denied despite apparently suitable modes: inspect the process identity, parent traversal, ACLs and security-policy evidence. Do not disable the system's protection without establishing the cause.<br />
</li>
</ul>
Several layers can be involved. Compare the same request at the correct layer without publishing private upstream services or weakening authentication.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">8. Be careful with proxy addresses</span><br />
An IP-based access rule may see a CDN/proxy address rather than the visitor address unless trusted real-IP handling is configured correctly. Do not trust arbitrary forwarded headers or whitelist large address ranges without understanding the effective client identity.<br />
Our test used loopback and a local HTTPS request with the correct hostname and certificate validation. It did not audit CDN rules or demonstrate all remote client conditions.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">9. Retain a rollback and share a small safe excerpt</span><br />
Keep the affected file's original ownership/mode and a backup before a real deployment repair. Retest the denied path, a normal page and relevant authenticated behavior. A new 200 alone is not a success if it accidentally makes protected content public.<br />
For support, include versions, time/timezone, path, status, recent change and the redacted matching message. Remove tokens, visitor IPs, private contact details and unrelated requests. Logs are evidence to interpret, not instructions to execute.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">References</span>: <a href="https://www.gnu.org/software/coreutils/manual/html_node/Mode-Structure.html" target="_blank" rel="noopener" class="mycode_url">GNU file-mode structure</a>, <a href="https://nginx.org/en/docs/http/ngx_http_access_module.html" target="_blank" rel="noopener" class="mycode_url">Nginx access rules</a> and <a href="https://nginx.org/en/docs/http/ngx_http_autoindex_module.html" target="_blank" rel="noopener" class="mycode_url">Nginx directory listings</a>.]]></description>
			<content:encoded><![CDATA[<span style="font-weight: bold;" class="mycode_b">A 403 Forbidden response means the request was denied, but it does not identify the rule or permission responsible. Read the matching error before changing file ownership, enabling directory listings or disabling security controls.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Tested</span>: aaPanel Free 8.0.7, Ubuntu 22.04.5 and Nginx 1.30.5. We created one temporary public-text fixture, withheld file access to reproduce a permission-related 403, then permitted group read on that file and verified HTTP 200. It was removed afterwards. No application directory, access rule or global service setting was changed.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">1. Identify the request and the layer denying it</span><br />
Record hostname/path, time/timezone, status, request method, client context and recent changes. Check whether one file, a directory URL, all website pages or only an authenticated action is denied.<br />
Cloudflare/WAF, Nginx and the application can each deny a request. A status or response logo is a clue, not proof of the source. A browser login failure may need account/application evidence rather than filesystem permissions.<br />
For a harmless GET you control:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>curl -sS --max-time 15 -D - -o /dev/null https://example.com/affected-path</code></div></div>Replace the hostname/path; use curl.exe and NUL on Windows. Keep certificate validation enabled. Do not test protected data by bypassing an access policy merely to get a successful response.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">2. Match the website's access and error logs</span><br />
In our aaPanel interface: Website → PHP Project → the exact site's Log button → Access log / Error log. Confirm the source path and use Refresh. Read <a href="https://adminsforum.com/Thread-guide-check-website-logs-in-aapanel-to-troubleshoot-site-errors" target="_blank" rel="noopener" class="mycode_url">the website log guide</a> for detailed searching and timestamp checks.<br />
Our test request appeared as 403 in the access log. The corresponding Nginx error named the temporary text file and reported:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>open() ... failed (13: Permission denied)</code></div></div>This supported a filesystem-access diagnosis for that static file. It was different from a missing file (404) or an undefined PHP function (500).<br />
<br />
<span style="font-weight: bold;" class="mycode_b">3. Inspect the exact file and its parent directories</span><br />
Before a repair, record permissions, owner and group:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>ls -l /path/to/affected-file
namei -l /path/to/affected-file</code></div></div>Use the actual path from the matching error. namei displays path components so you can inspect parent-directory traversal as well as the final file. Availability depends on your Linux tools.<br />
A server needs appropriate read access to a served static file and search/traverse access to its parent directories. An unreadable file and a non-traversable parent require different targeted changes. ACLs and platform security controls can also affect access; mode bits alone do not explain every denial.<br />
Our fixture's parents were traversable, and the denied file had mode 000 with owner root and group www. No parent permissions were changed during the test.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">4. Confirm the process that needs access</span><br />
Our Nginx master ran as root, but its workers ran as www:www. The worker identity is the relevant one for this static request; an administrator being able to read the file does not prove the web worker can.<br />
A useful check that avoids full process arguments is:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>ps -eo user,group,comm</code></div></div>Inspect the Nginx rows. Do not assume that every panel uses www: different services and PHP pools can run under different users. Ownership should follow your deployment model, not a blanket command copied from another server.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">5. Apply the smallest justified permission change</span><br />
For our existing fixture ownership root:www, mode 640 gave the owner read/write, the group read and others no access. Because the workers used group www, this permitted the intended static response without allowing them to write to the file.<br />
Our one-file repair was equivalent to:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>chmod 640 /www/wwwroot/adminslab.com/af-http403-lab-20261014.txt</code></div></div>That path was our disposable text fixture. Before adapting the command, verify the intended owner/group and runtime identity, record the original mode and select only the affected file. It does not change ownership or grant a different group access.<br />
This was a file-specific lab example, not a universal website permission preset. Other deployments may use another group or access model. A directory needs traversal permissions, so do not apply file mode 640 recursively to directories.<br />
The existing guide <a href="https://adminsforum.com/Thread-guide-set-file-permissions-and-ownership-in-aapanel" target="_blank" rel="noopener" class="mycode_url">Set File Permissions and Ownership in aaPanel</a> covers the panel controls. Preserve the original mode/owner/group so a real repair can be reversed. Do not recursively apply chmod 777 or give a runtime write access to all application code just to eliminate a 403.<br />
<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://adminsforum.com/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=319" target="_blank" title="">http403-permission-20261014.jpg</a> (Size: 131.87 KB / Downloads: 7)
<!-- end: postbit_attachments_attachment --><br />
<span style="font-style: italic;" class="mycode_i">AdminsForum formatted transcript of the original experiment, not a panel screenshot.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">6. Verify the response and new log evidence</span><br />
After the targeted file change, the same URL returned HTTP 200 with the expected text. The access log recorded 200, no new error entry matched that file, and the normal homepage also returned 200.<br />
No Nginx reload or restart was needed for this filesystem-mode change. If you instead edit a server configuration, validate it and use the supported reload procedure before testing its behavior.<br />
We removed the temporary fixture after verification. The deliberately inaccessible file should not be left as a public test URL. Existing application files and permissions remained untouched.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">7. Distinguish other 403 causes</span><br />
The following are investigation examples, not failures induced in this test:<ul class="mycode_list"><li>Directory URL denied: inspect the intended default document, running/document root and directory-index configuration. A missing expected index is not a reason to expose a directory listing of website files.<br />
</li>
<li>Access-rule denial: inspect the matching Nginx location and ordered allow/deny rules, or the applicable panel access restriction. Confirm whether the denial is intentional before changing it.<br />
</li>
<li>WAF/CDN denial: inspect the corresponding security event and request context. Resolve a verified false positive narrowly; do not disable the whole firewall or WAF as a shortcut.<br />
</li>
<li>Application denial: inspect account role, authentication/session state, CSRF checks and the application logger. A filesystem chmod will not correct an authorization decision.<br />
</li>
<li>Permission denied despite apparently suitable modes: inspect the process identity, parent traversal, ACLs and security-policy evidence. Do not disable the system's protection without establishing the cause.<br />
</li>
</ul>
Several layers can be involved. Compare the same request at the correct layer without publishing private upstream services or weakening authentication.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">8. Be careful with proxy addresses</span><br />
An IP-based access rule may see a CDN/proxy address rather than the visitor address unless trusted real-IP handling is configured correctly. Do not trust arbitrary forwarded headers or whitelist large address ranges without understanding the effective client identity.<br />
Our test used loopback and a local HTTPS request with the correct hostname and certificate validation. It did not audit CDN rules or demonstrate all remote client conditions.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">9. Retain a rollback and share a small safe excerpt</span><br />
Keep the affected file's original ownership/mode and a backup before a real deployment repair. Retest the denied path, a normal page and relevant authenticated behavior. A new 200 alone is not a success if it accidentally makes protected content public.<br />
For support, include versions, time/timezone, path, status, recent change and the redacted matching message. Remove tokens, visitor IPs, private contact details and unrelated requests. Logs are evidence to interpret, not instructions to execute.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">References</span>: <a href="https://www.gnu.org/software/coreutils/manual/html_node/Mode-Structure.html" target="_blank" rel="noopener" class="mycode_url">GNU file-mode structure</a>, <a href="https://nginx.org/en/docs/http/ngx_http_access_module.html" target="_blank" rel="noopener" class="mycode_url">Nginx access rules</a> and <a href="https://nginx.org/en/docs/http/ngx_http_autoindex_module.html" target="_blank" rel="noopener" class="mycode_url">Nginx directory listings</a>.]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[Monitor Server Resources During aaPanel Website Traffic Tests]]></title>
			<link>https://adminsforum.com/Thread-guide-monitor-server-resources-during-aapanel-website-traffic-tests</link>
			<pubDate>Wed, 14 Oct 2026 20:51:09 +0800</pubDate>
			<dc:creator><![CDATA[<a href="https://adminsforum.com/member.php?action=profile&uid=2">DEV</a>]]></dc:creator>
			<guid isPermaLink="false">https://adminsforum.com/Thread-guide-monitor-server-resources-during-aapanel-website-traffic-tests</guid>
			<description><![CDATA[<span style="font-weight: bold;" class="mycode_b">Response times tell you that a site slowed down; simultaneous server measurements help explain why. This guide pairs a bounded WordPress traffic test with CPU, memory, swap and I/O observations on an aaPanel lab VPS.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Test environment</span>: aaPanel Free 8.0.7, Ubuntu 22.04.5, two virtual CPUs and about 3.8GiB RAM, with Nginx, WordPress and PHP 8.5. The server also runs database, mail and other lab services. A separate Windows client generated traffic. We changed no server settings for this run.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">1. Define the question before generating traffic</span><br />
Are slow responses accompanied by CPU activity, memory pressure, storage waiting or application queueing? Choose a harmless lab endpoint and collect a baseline. Do not test destructive application actions or run an unlimited traffic loop.<br />
Our test used the WordPress homepage with unique query strings. The existing FastCGI rules bypass query-string requests, verified in the earlier cache guide. A cached anonymous homepage could hide the PHP work we wanted to observe.<br />
Start with <a href="https://adminsforum.com/Thread-guide-run-a-controlled-wordpress-load-test-on-an-aapanel-vps" target="_blank" rel="noopener" class="mycode_url">the controlled load-test guide</a> for request limits, TLS checks and timing definitions. This follow-up adds simultaneous resource evidence rather than repeating a capacity claim.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">2. Capture memory and load snapshots</span><br />
On the lab server, use a private SSH session:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>date -Is
uptime
free -h
cat /proc/meminfo</code></div></div>Record the clock and timezone. The client and server must have reasonably aligned clocks to correlate events. Low free memory alone does not demonstrate a problem; include available memory and swap activity. A load average is also not a direct CPU percentage.<br />
Our MemAvailable snapshots were 2,849,192KiB before and 2,813,252KiB after, approximately 2.72GiB and 2.68GiB. These are two snapshots, not the minimum available memory throughout the run.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">3. Start a finite interval sampler</span><br />
Our existing vmstat utility required no additional package installation. We ran:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>LC_ALL=C vmstat -n -t 1 25</code></div></div>This requests one-second updates and a finite count. Keep the output in a private administration session or log, and note the sampler's start/end time. It should overlap the request phases, not run only after the workload finishes.<br />
The first data row includes activity averages since boot. We excluded it from interval summaries. Process and memory columns are snapshots; later activity columns cover the sampling interval. In this run we retained 24 interval rows across the traffic and surrounding idle time.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">4. Understand the useful columns</span><ul class="mycode_list"><li>r: runnable work; compare repeated values with the CPU count, not one isolated spike.<br />
</li>
<li>b: work blocked waiting for I/O completion.<br />
</li>
<li>us and sy: user and kernel CPU percentages. Our CPU activity summary used 100 minus idle, I/O wait and steal.<br />
</li>
<li>id, wa and st: idle, I/O-wait and virtual-machine steal percentages; these describe different conditions.<br />
</li>
<li>si and so: swap-in and swap-out activity. Existing swap usage without current transfers is different from sustained swapping.<br />
</li>
<li>bi and bo: block input/output activity. These alone do not identify which disk or application is responsible.<br />
</li>
</ul>
Check the local manual for units and version details. Small one-second samples can hide shorter spikes, while whole-server measurements combine activity from every service.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">5. Run a bounded client workload</span><br />
We used 80 requests at concurrency 4, followed by 80 at concurrency 8. Every request used HTTPS with normal certificate validation, a 15-second maximum time and no retry. The run would stop before a later phase if any request failed or its sample p95 TTFB exceeded five seconds.<br />
The generator ran off the VPS, so its local client work did not consume the VPS CPUs being measured. It used a finite worker pool: after one request completed, a worker could submit another. This closed-loop workload does not reproduce every real traffic pattern.<br />
Keep important service health under observation and stop if your lab becomes unstable. Do not increase concurrency just to force a failure.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">6. Compare the recorded results</span><br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://adminsforum.com/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=297" target="_blank" title="">monitor-load-20261014.jpg</a> (Size: 128.09 KB / Downloads: 9)
<!-- end: postbit_attachments_attachment --><br />
<span style="font-style: italic;" class="mycode_i">AdminsForum formatted transcript of the original measurements, not a terminal screenshot.</span><br />
<ul class="mycode_list"><li>Concurrency 4: 80/80 HTTP 200, median TTFB 313ms, sample p95 467ms, elapsed phase about 10.20 seconds.<br />
</li>
<li>Concurrency 8: 80/80 HTTP 200, median TTFB 414ms, sample p95 539ms, elapsed phase about 5.76 seconds.<br />
</li>
<li>Across the 24 resource intervals: CPU activity peaked at 88%, I/O wait at 0%, steal at 1%, and runnable work at 6.<br />
</li>
<li>Every interval reported zero swap-in and zero swap-out; available-memory snapshots remained about 2.7GiB.<br />
</li>
</ul>
The higher-concurrency phase completed its finite batch sooner but had higher median latency. These short phases were not repeated in randomized order, and other lab services may contribute to whole-server CPU activity.<br />
The resource summary includes idle time around the test. Its peak values should not be confused with a phase average. We did not measure FPM queue lengths, individual query timing, per-process CPU attribution or device latency in this run.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">7. Form a hypothesis without overstating it</span><br />
The observations do not show sampled swap pressure or I/O-wait activity. CPU work is a plausible next investigation because activity increased during this request workload. They do not prove that CPU is the only bottleneck or that the VPS can sustain this traffic indefinitely.<br />
For a stronger diagnosis, correlate process CPU, private FPM queue/status observations and application timings with the same request timestamps. Read <a href="https://adminsforum.com/Thread-guide-find-slow-php-requests-in-aapanel-with-php-fpm-slow-logs" target="_blank" rel="noopener" class="mycode_url">the PHP slow-log guide</a> to locate executing requests and <a href="https://adminsforum.com/Thread-guide-tune-mariadb-queries-in-aapanel-with-explain-and-index-tests" target="_blank" rel="noopener" class="mycode_url">the database index guide</a> when SQL is implicated.<br />
If storage is suspected, collect device-level latency and queue information as a separate test. A sampled wa value of zero is not proof that every storage operation was instantaneous.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">8. Choose the next change from evidence</span><br />
CPU-heavy PHP work may benefit from application improvements or appropriate caching; adding workers can increase contention. Memory pressure calls for examining actual process use and service budgets. Slow SQL calls for a query plan, not an arbitrary increase to max_connections.<br />
Change one variable, retain a rollback copy and repeat the same workload with the sampler running. Check content correctness and errors as well as speed. HTTP 200 alone cannot establish that an application returned the intended page.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">9. Keep the test reproducible</span><br />
Save phase times, raw request outcomes, resource intervals and configuration versions. Keep logs private and remove secrets before sharing. Use representative authenticated and write workflows only in an appropriate disposable test environment.<br />
This test added no public diagnostic endpoint, altered no application data and left server settings unchanged. Longer runs and realistic workload mixes remain necessary for production planning.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Reference</span>: <a href="https://manpages.ubuntu.com/manpages/jammy/man8/vmstat.8.html" target="_blank" rel="noopener" class="mycode_url">Ubuntu vmstat manual</a>.]]></description>
			<content:encoded><![CDATA[<span style="font-weight: bold;" class="mycode_b">Response times tell you that a site slowed down; simultaneous server measurements help explain why. This guide pairs a bounded WordPress traffic test with CPU, memory, swap and I/O observations on an aaPanel lab VPS.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Test environment</span>: aaPanel Free 8.0.7, Ubuntu 22.04.5, two virtual CPUs and about 3.8GiB RAM, with Nginx, WordPress and PHP 8.5. The server also runs database, mail and other lab services. A separate Windows client generated traffic. We changed no server settings for this run.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">1. Define the question before generating traffic</span><br />
Are slow responses accompanied by CPU activity, memory pressure, storage waiting or application queueing? Choose a harmless lab endpoint and collect a baseline. Do not test destructive application actions or run an unlimited traffic loop.<br />
Our test used the WordPress homepage with unique query strings. The existing FastCGI rules bypass query-string requests, verified in the earlier cache guide. A cached anonymous homepage could hide the PHP work we wanted to observe.<br />
Start with <a href="https://adminsforum.com/Thread-guide-run-a-controlled-wordpress-load-test-on-an-aapanel-vps" target="_blank" rel="noopener" class="mycode_url">the controlled load-test guide</a> for request limits, TLS checks and timing definitions. This follow-up adds simultaneous resource evidence rather than repeating a capacity claim.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">2. Capture memory and load snapshots</span><br />
On the lab server, use a private SSH session:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>date -Is
uptime
free -h
cat /proc/meminfo</code></div></div>Record the clock and timezone. The client and server must have reasonably aligned clocks to correlate events. Low free memory alone does not demonstrate a problem; include available memory and swap activity. A load average is also not a direct CPU percentage.<br />
Our MemAvailable snapshots were 2,849,192KiB before and 2,813,252KiB after, approximately 2.72GiB and 2.68GiB. These are two snapshots, not the minimum available memory throughout the run.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">3. Start a finite interval sampler</span><br />
Our existing vmstat utility required no additional package installation. We ran:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>LC_ALL=C vmstat -n -t 1 25</code></div></div>This requests one-second updates and a finite count. Keep the output in a private administration session or log, and note the sampler's start/end time. It should overlap the request phases, not run only after the workload finishes.<br />
The first data row includes activity averages since boot. We excluded it from interval summaries. Process and memory columns are snapshots; later activity columns cover the sampling interval. In this run we retained 24 interval rows across the traffic and surrounding idle time.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">4. Understand the useful columns</span><ul class="mycode_list"><li>r: runnable work; compare repeated values with the CPU count, not one isolated spike.<br />
</li>
<li>b: work blocked waiting for I/O completion.<br />
</li>
<li>us and sy: user and kernel CPU percentages. Our CPU activity summary used 100 minus idle, I/O wait and steal.<br />
</li>
<li>id, wa and st: idle, I/O-wait and virtual-machine steal percentages; these describe different conditions.<br />
</li>
<li>si and so: swap-in and swap-out activity. Existing swap usage without current transfers is different from sustained swapping.<br />
</li>
<li>bi and bo: block input/output activity. These alone do not identify which disk or application is responsible.<br />
</li>
</ul>
Check the local manual for units and version details. Small one-second samples can hide shorter spikes, while whole-server measurements combine activity from every service.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">5. Run a bounded client workload</span><br />
We used 80 requests at concurrency 4, followed by 80 at concurrency 8. Every request used HTTPS with normal certificate validation, a 15-second maximum time and no retry. The run would stop before a later phase if any request failed or its sample p95 TTFB exceeded five seconds.<br />
The generator ran off the VPS, so its local client work did not consume the VPS CPUs being measured. It used a finite worker pool: after one request completed, a worker could submit another. This closed-loop workload does not reproduce every real traffic pattern.<br />
Keep important service health under observation and stop if your lab becomes unstable. Do not increase concurrency just to force a failure.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">6. Compare the recorded results</span><br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://adminsforum.com/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=297" target="_blank" title="">monitor-load-20261014.jpg</a> (Size: 128.09 KB / Downloads: 9)
<!-- end: postbit_attachments_attachment --><br />
<span style="font-style: italic;" class="mycode_i">AdminsForum formatted transcript of the original measurements, not a terminal screenshot.</span><br />
<ul class="mycode_list"><li>Concurrency 4: 80/80 HTTP 200, median TTFB 313ms, sample p95 467ms, elapsed phase about 10.20 seconds.<br />
</li>
<li>Concurrency 8: 80/80 HTTP 200, median TTFB 414ms, sample p95 539ms, elapsed phase about 5.76 seconds.<br />
</li>
<li>Across the 24 resource intervals: CPU activity peaked at 88%, I/O wait at 0%, steal at 1%, and runnable work at 6.<br />
</li>
<li>Every interval reported zero swap-in and zero swap-out; available-memory snapshots remained about 2.7GiB.<br />
</li>
</ul>
The higher-concurrency phase completed its finite batch sooner but had higher median latency. These short phases were not repeated in randomized order, and other lab services may contribute to whole-server CPU activity.<br />
The resource summary includes idle time around the test. Its peak values should not be confused with a phase average. We did not measure FPM queue lengths, individual query timing, per-process CPU attribution or device latency in this run.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">7. Form a hypothesis without overstating it</span><br />
The observations do not show sampled swap pressure or I/O-wait activity. CPU work is a plausible next investigation because activity increased during this request workload. They do not prove that CPU is the only bottleneck or that the VPS can sustain this traffic indefinitely.<br />
For a stronger diagnosis, correlate process CPU, private FPM queue/status observations and application timings with the same request timestamps. Read <a href="https://adminsforum.com/Thread-guide-find-slow-php-requests-in-aapanel-with-php-fpm-slow-logs" target="_blank" rel="noopener" class="mycode_url">the PHP slow-log guide</a> to locate executing requests and <a href="https://adminsforum.com/Thread-guide-tune-mariadb-queries-in-aapanel-with-explain-and-index-tests" target="_blank" rel="noopener" class="mycode_url">the database index guide</a> when SQL is implicated.<br />
If storage is suspected, collect device-level latency and queue information as a separate test. A sampled wa value of zero is not proof that every storage operation was instantaneous.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">8. Choose the next change from evidence</span><br />
CPU-heavy PHP work may benefit from application improvements or appropriate caching; adding workers can increase contention. Memory pressure calls for examining actual process use and service budgets. Slow SQL calls for a query plan, not an arbitrary increase to max_connections.<br />
Change one variable, retain a rollback copy and repeat the same workload with the sampler running. Check content correctness and errors as well as speed. HTTP 200 alone cannot establish that an application returned the intended page.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">9. Keep the test reproducible</span><br />
Save phase times, raw request outcomes, resource intervals and configuration versions. Keep logs private and remove secrets before sharing. Use representative authenticated and write workflows only in an appropriate disposable test environment.<br />
This test added no public diagnostic endpoint, altered no application data and left server settings unchanged. Longer runs and realistic workload mixes remain necessary for production planning.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Reference</span>: <a href="https://manpages.ubuntu.com/manpages/jammy/man8/vmstat.8.html" target="_blank" rel="noopener" class="mycode_url">Ubuntu vmstat manual</a>.]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[Check Website Logs in aaPanel to Troubleshoot Site Errors]]></title>
			<link>https://adminsforum.com/Thread-guide-check-website-logs-in-aapanel-to-troubleshoot-site-errors</link>
			<pubDate>Wed, 14 Oct 2026 20:48:47 +0800</pubDate>
			<dc:creator><![CDATA[<a href="https://adminsforum.com/member.php?action=profile&uid=2">DEV</a>]]></dc:creator>
			<guid isPermaLink="false">https://adminsforum.com/Thread-guide-check-website-logs-in-aapanel-to-troubleshoot-site-errors</guid>
			<description><![CDATA[<span style="font-weight: bold;" class="mycode_b">Start website troubleshooting by recording the failing request, its time and its HTTP status. Then match that request with the website's access log, error log and application evidence. This guide shows a real missing-file diagnosis in aaPanel and explains where to look for other failures.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Tested</span>: aaPanel Free 8.0.7, Ubuntu 22.04.5 and Nginx 1.30.5. We inspected the native website log viewer, generated one missing-file request, matched its access/error entries and verified a temporary file corrected that request. The test file was removed afterwards. Other error types below are interpretation examples, not deliberately triggered failures.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">1. Write down the symptom before changing anything</span><br />
Record the exact hostname and path, time with timezone, HTTP status, request method, whether all pages fail or one route fails, and the last relevant change. Distinguish a browser connection failure from an HTTP response delivered by a server.<br />
A request may pass through Cloudflare/CDN, Nginx, PHP-FPM and the application. A status code alone does not identify which layer caused it. Avoid restarting every service before collecting the relevant evidence.<br />
For a harmless GET endpoint you control:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>curl -sS --max-time 15 -D - -o /dev/null https://example.com/problem-path</code></div></div>Replace the hostname and path. Keep certificate verification enabled. This Linux example shows headers and discards the body; on Windows use curl.exe and NUL. Separately inspect the response content, because HTTP 200 can still contain an application error.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">2. Open the website's native aaPanel logs</span><br />
In our current interface:<ul class="mycode_list"><li>Open Website and select PHP Project.<br />
</li>
<li>Find the exact site row and click Log.<br />
</li>
<li>The Site modification window provides Access log and Error log views, alongside Response log and Log Security Analysis.<br />
</li>
<li>Use Refresh to retrieve current information. The access view offers recent 50, 100, 500 or All lines and displays the source log path.<br />
</li>
</ul>
Labels differ between versions and project types. The main left-menu Logs section is a separate panel-wide entry; do not assume its administrative log is the website's application log. Node/Python projects have their own runtime logging to inspect as well.<br />
Our website access log was /www/wwwlogs/adminslab.com.log; its error log was /www/wwwlogs/adminslab.com.error.log. These are lab paths, not universal names. Confirm the selected website and effective Nginx access_log/error_log directives if the viewer shows no matching entries.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">3. Locate the request in the access log</span><br />
The access log tells you that the server handled a request and records configured fields such as method, path, status and byte count. Our matching entry contained:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>[14/Oct/2026:12:40:54 +0000]
GET /af-log-troubleshooting-missing-20261014.txt HTTP/2.0
404</code></div></div>The full entry used loopback client 127.0.0.1 because our request came from the server itself. For proxied visitor traffic, an address in a log may identify the proxy unless trusted real-IP handling is configured. Do not treat arbitrary forwarded headers as verified visitor identity.<br />
Match the time, hostname, path and method rather than assuming the latest error belongs to your browser. Check browser/CDN caching and timezone differences when the times seem inconsistent.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">4. Match the error log at the same time</span><br />
At 12:40:54 UTC, our error log reported an open() failure for the expected file under /www/wwwroot/adminslab.com, with error 2: No such file or directory.<br />
This supported a missing-file diagnosis for this static test URL. We created only that temporary file at the expected root path and repeated the request. At 12:41:34 UTC the response was HTTP 200 with the intended test content, and the access log recorded 200.<br />
<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://adminsforum.com/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=296" target="_blank" title="">log-troubleshooting-20261014.jpg</a> (Size: 129.35 KB / Downloads: 8)
<!-- end: postbit_attachments_attachment --><br />
<span style="font-style: italic;" class="mycode_i">AdminsForum formatted transcript of our synthetic request and matching log entries, not a screenshot. Unrelated visitor entries are excluded.</span><br />
<br />
We removed the fixture after verification. This was a lab demonstration, not a recommendation to create an empty file whenever a route returns 404. An application route may instead require the right document root or rewrite rules. For WordPress pretty URLs, see <a href="https://adminsforum.com/Thread-guide-fix-wordpress-permalink-404s-in-aapanel-free-edition-nginx" target="_blank" rel="noopener" class="mycode_url">the permalink 404 guide</a>.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">5. Read common messages as clues</span><ul class="mycode_list"><li>No such file or directory: inspect the exact path, document root, deployment contents or upstream socket named by the message. A missing static file and a missing FPM socket have different fixes.<br />
</li>
<li>Permission denied: identify the object, runtime user and required access. Review ownership, parent-directory traversal and applicable security controls; do not recursively apply chmod 777.<br />
</li>
<li>Connection refused while connecting to upstream: inspect the configured upstream and whether the intended service is listening. Check its own error/startup logs before restarting it.<br />
</li>
<li>Upstream timed out: investigate the upstream's workload and dependencies. Raising a timeout without finding the delay can keep resources occupied longer.<br />
</li>
<li>PHP fatal/uncaught exception: locate the application stack and matching runtime log, then review the relevant code, dependency and recent change. A 500 is not always a PHP syntax error.<br />
</li>
</ul>
These are example interpretations. Use the complete message and request context. For 502 troubleshooting, correlate web-server and upstream evidence; do not use HTTP curl against a FastCGI socket as though it were an HTTP endpoint.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">6. Know the difference between PHP-FPM and application logs</span><br />
The lab FPM master error_log was /www/server/php/85/var/log/php-fpm.log. This captures FPM service diagnostics; it is not a guarantee that every PHP application's warning appears there.<br />
A private FPM settings probe confirmed PHP loaded /www/server/php/85/etc/php.ini, with log_errors enabled, display_errors off and no explicit PHP error_log path. An empty PHP error_log value delegates the destination to the SAPI. Inspect the effective runtime, web-server FastCGI error output and application logger rather than assuming a file name.<br />
The CLI runtime can load a different ini file. Never publish phpinfo or stack traces on an unprotected website just to discover a setting. Our probe was outside the web root, used through the private socket and removed afterwards.<br />
PHP slow logs answer a different question: where a long-running worker was executing. Follow <a href="https://adminsforum.com/Thread-guide-find-slow-php-requests-in-aapanel-with-php-fpm-slow-logs" target="_blank" rel="noopener" class="mycode_url">the tested slow-log guide</a> for that workflow.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">7. Check the application logger</span><br />
Frameworks and CMS plugins may capture exceptions in their own log location or service journal. Check the application's documented logging configuration and make sure the runtime can write to its intended private directory.<br />
For WordPress, official debug logging supports a chosen file path while display can remain disabled. Use a staging copy and backup before temporary debug changes; do not expose wp-content/debug.log publicly or leave development debugging enabled on a live site. This article did not alter WordPress debug settings.<br />
For a database-related exception, examine the corresponding database evidence and query. A web-server access log usually cannot tell you which SQL statement was slow or failed.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">8. Search bounded log output through SSH</span><br />
If the panel cannot reach an older entry, use the actual private log paths:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>tail -n 100 /www/wwwlogs/example.com.error.log
grep -F -- '/problem-path' /www/wwwlogs/example.com.log
tail -F /www/wwwlogs/example.com.error.log</code></div></div>Replace example.com and the path with your own. tail -n shows recent lines; grep -F matches literal text; tail -F follows a log and can handle its replacement during rotation. Stop live following with Ctrl+C. For very large files, restrict the time/file range instead of dumping everything.<br />
A rotated log may contain yesterday's evidence. Read the appropriate archive with supported tools; do not clear or delete logs merely to make the view shorter. Preserve useful evidence and maintain deliberate retention.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">9. When no matching entry appears</span><br />
Confirm that the request reached the expected server and virtual host. Check DNS, CDN responses, HTTPS/redirect paths, selected website, enabled logging and rotation. Some locations intentionally suppress logging; absence of an entry does not prove the request never happened.<br />
If the web server cannot start, a per-site access log may be irrelevant. Inspect the server's startup/configuration error and validate the actual panel-managed configuration. For our Nginx installation the syntax check is:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/www/server/nginx/sbin/nginx -t</code></div></div>Do not reload an invalid configuration. Retain a backup before a repair and check normal pages after applying it.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">10. Verify the fix and ask for help with safe evidence</span><br />
Change one identified cause, repeat the same request, inspect the new status and content, and confirm the matching fresh log entry. Check a normal page too. An old error remaining in the log does not mean the fix failed: compare timestamps.<br />
When posting support details, include versions, affected path, time/timezone, status, last change and a small redacted matching excerpt. Remove credentials, cookies, tokens, private email addresses, visitor IPs and unrelated user data. Logs are evidence to interpret, not instructions to execute.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">References</span>: <a href="https://nginx.org/en/docs/beginners_guide.html" target="_blank" rel="noopener" class="mycode_url">Nginx beginner's guide</a>, <a href="https://www.php.net/manual/en/errorfunc.configuration.php" target="_blank" rel="noopener" class="mycode_url">PHP error logging configuration</a> and <a href="https://developer.wordpress.org/advanced-administration/debug/debug-wordpress/" target="_blank" rel="noopener" class="mycode_url">WordPress debugging documentation</a>.]]></description>
			<content:encoded><![CDATA[<span style="font-weight: bold;" class="mycode_b">Start website troubleshooting by recording the failing request, its time and its HTTP status. Then match that request with the website's access log, error log and application evidence. This guide shows a real missing-file diagnosis in aaPanel and explains where to look for other failures.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Tested</span>: aaPanel Free 8.0.7, Ubuntu 22.04.5 and Nginx 1.30.5. We inspected the native website log viewer, generated one missing-file request, matched its access/error entries and verified a temporary file corrected that request. The test file was removed afterwards. Other error types below are interpretation examples, not deliberately triggered failures.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">1. Write down the symptom before changing anything</span><br />
Record the exact hostname and path, time with timezone, HTTP status, request method, whether all pages fail or one route fails, and the last relevant change. Distinguish a browser connection failure from an HTTP response delivered by a server.<br />
A request may pass through Cloudflare/CDN, Nginx, PHP-FPM and the application. A status code alone does not identify which layer caused it. Avoid restarting every service before collecting the relevant evidence.<br />
For a harmless GET endpoint you control:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>curl -sS --max-time 15 -D - -o /dev/null https://example.com/problem-path</code></div></div>Replace the hostname and path. Keep certificate verification enabled. This Linux example shows headers and discards the body; on Windows use curl.exe and NUL. Separately inspect the response content, because HTTP 200 can still contain an application error.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">2. Open the website's native aaPanel logs</span><br />
In our current interface:<ul class="mycode_list"><li>Open Website and select PHP Project.<br />
</li>
<li>Find the exact site row and click Log.<br />
</li>
<li>The Site modification window provides Access log and Error log views, alongside Response log and Log Security Analysis.<br />
</li>
<li>Use Refresh to retrieve current information. The access view offers recent 50, 100, 500 or All lines and displays the source log path.<br />
</li>
</ul>
Labels differ between versions and project types. The main left-menu Logs section is a separate panel-wide entry; do not assume its administrative log is the website's application log. Node/Python projects have their own runtime logging to inspect as well.<br />
Our website access log was /www/wwwlogs/adminslab.com.log; its error log was /www/wwwlogs/adminslab.com.error.log. These are lab paths, not universal names. Confirm the selected website and effective Nginx access_log/error_log directives if the viewer shows no matching entries.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">3. Locate the request in the access log</span><br />
The access log tells you that the server handled a request and records configured fields such as method, path, status and byte count. Our matching entry contained:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>[14/Oct/2026:12:40:54 +0000]
GET /af-log-troubleshooting-missing-20261014.txt HTTP/2.0
404</code></div></div>The full entry used loopback client 127.0.0.1 because our request came from the server itself. For proxied visitor traffic, an address in a log may identify the proxy unless trusted real-IP handling is configured. Do not treat arbitrary forwarded headers as verified visitor identity.<br />
Match the time, hostname, path and method rather than assuming the latest error belongs to your browser. Check browser/CDN caching and timezone differences when the times seem inconsistent.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">4. Match the error log at the same time</span><br />
At 12:40:54 UTC, our error log reported an open() failure for the expected file under /www/wwwroot/adminslab.com, with error 2: No such file or directory.<br />
This supported a missing-file diagnosis for this static test URL. We created only that temporary file at the expected root path and repeated the request. At 12:41:34 UTC the response was HTTP 200 with the intended test content, and the access log recorded 200.<br />
<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://adminsforum.com/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=296" target="_blank" title="">log-troubleshooting-20261014.jpg</a> (Size: 129.35 KB / Downloads: 8)
<!-- end: postbit_attachments_attachment --><br />
<span style="font-style: italic;" class="mycode_i">AdminsForum formatted transcript of our synthetic request and matching log entries, not a screenshot. Unrelated visitor entries are excluded.</span><br />
<br />
We removed the fixture after verification. This was a lab demonstration, not a recommendation to create an empty file whenever a route returns 404. An application route may instead require the right document root or rewrite rules. For WordPress pretty URLs, see <a href="https://adminsforum.com/Thread-guide-fix-wordpress-permalink-404s-in-aapanel-free-edition-nginx" target="_blank" rel="noopener" class="mycode_url">the permalink 404 guide</a>.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">5. Read common messages as clues</span><ul class="mycode_list"><li>No such file or directory: inspect the exact path, document root, deployment contents or upstream socket named by the message. A missing static file and a missing FPM socket have different fixes.<br />
</li>
<li>Permission denied: identify the object, runtime user and required access. Review ownership, parent-directory traversal and applicable security controls; do not recursively apply chmod 777.<br />
</li>
<li>Connection refused while connecting to upstream: inspect the configured upstream and whether the intended service is listening. Check its own error/startup logs before restarting it.<br />
</li>
<li>Upstream timed out: investigate the upstream's workload and dependencies. Raising a timeout without finding the delay can keep resources occupied longer.<br />
</li>
<li>PHP fatal/uncaught exception: locate the application stack and matching runtime log, then review the relevant code, dependency and recent change. A 500 is not always a PHP syntax error.<br />
</li>
</ul>
These are example interpretations. Use the complete message and request context. For 502 troubleshooting, correlate web-server and upstream evidence; do not use HTTP curl against a FastCGI socket as though it were an HTTP endpoint.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">6. Know the difference between PHP-FPM and application logs</span><br />
The lab FPM master error_log was /www/server/php/85/var/log/php-fpm.log. This captures FPM service diagnostics; it is not a guarantee that every PHP application's warning appears there.<br />
A private FPM settings probe confirmed PHP loaded /www/server/php/85/etc/php.ini, with log_errors enabled, display_errors off and no explicit PHP error_log path. An empty PHP error_log value delegates the destination to the SAPI. Inspect the effective runtime, web-server FastCGI error output and application logger rather than assuming a file name.<br />
The CLI runtime can load a different ini file. Never publish phpinfo or stack traces on an unprotected website just to discover a setting. Our probe was outside the web root, used through the private socket and removed afterwards.<br />
PHP slow logs answer a different question: where a long-running worker was executing. Follow <a href="https://adminsforum.com/Thread-guide-find-slow-php-requests-in-aapanel-with-php-fpm-slow-logs" target="_blank" rel="noopener" class="mycode_url">the tested slow-log guide</a> for that workflow.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">7. Check the application logger</span><br />
Frameworks and CMS plugins may capture exceptions in their own log location or service journal. Check the application's documented logging configuration and make sure the runtime can write to its intended private directory.<br />
For WordPress, official debug logging supports a chosen file path while display can remain disabled. Use a staging copy and backup before temporary debug changes; do not expose wp-content/debug.log publicly or leave development debugging enabled on a live site. This article did not alter WordPress debug settings.<br />
For a database-related exception, examine the corresponding database evidence and query. A web-server access log usually cannot tell you which SQL statement was slow or failed.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">8. Search bounded log output through SSH</span><br />
If the panel cannot reach an older entry, use the actual private log paths:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>tail -n 100 /www/wwwlogs/example.com.error.log
grep -F -- '/problem-path' /www/wwwlogs/example.com.log
tail -F /www/wwwlogs/example.com.error.log</code></div></div>Replace example.com and the path with your own. tail -n shows recent lines; grep -F matches literal text; tail -F follows a log and can handle its replacement during rotation. Stop live following with Ctrl+C. For very large files, restrict the time/file range instead of dumping everything.<br />
A rotated log may contain yesterday's evidence. Read the appropriate archive with supported tools; do not clear or delete logs merely to make the view shorter. Preserve useful evidence and maintain deliberate retention.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">9. When no matching entry appears</span><br />
Confirm that the request reached the expected server and virtual host. Check DNS, CDN responses, HTTPS/redirect paths, selected website, enabled logging and rotation. Some locations intentionally suppress logging; absence of an entry does not prove the request never happened.<br />
If the web server cannot start, a per-site access log may be irrelevant. Inspect the server's startup/configuration error and validate the actual panel-managed configuration. For our Nginx installation the syntax check is:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/www/server/nginx/sbin/nginx -t</code></div></div>Do not reload an invalid configuration. Retain a backup before a repair and check normal pages after applying it.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">10. Verify the fix and ask for help with safe evidence</span><br />
Change one identified cause, repeat the same request, inspect the new status and content, and confirm the matching fresh log entry. Check a normal page too. An old error remaining in the log does not mean the fix failed: compare timestamps.<br />
When posting support details, include versions, affected path, time/timezone, status, last change and a small redacted matching excerpt. Remove credentials, cookies, tokens, private email addresses, visitor IPs and unrelated user data. Logs are evidence to interpret, not instructions to execute.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">References</span>: <a href="https://nginx.org/en/docs/beginners_guide.html" target="_blank" rel="noopener" class="mycode_url">Nginx beginner's guide</a>, <a href="https://www.php.net/manual/en/errorfunc.configuration.php" target="_blank" rel="noopener" class="mycode_url">PHP error logging configuration</a> and <a href="https://developer.wordpress.org/advanced-administration/debug/debug-wordpress/" target="_blank" rel="noopener" class="mycode_url">WordPress debugging documentation</a>.]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[Verify WordPress FastCGI Cache Hits and Bypass Rules in aaPanel]]></title>
			<link>https://adminsforum.com/Thread-guide-verify-wordpress-fastcgi-cache-hits-and-bypass-rules-in-aapanel</link>
			<pubDate>Wed, 14 Oct 2026 20:24:21 +0800</pubDate>
			<dc:creator><![CDATA[<a href="https://adminsforum.com/member.php?action=profile&uid=2">DEV</a>]]></dc:creator>
			<guid isPermaLink="false">https://adminsforum.com/Thread-guide-verify-wordpress-fastcgi-cache-hits-and-bypass-rules-in-aapanel</guid>
			<description><![CDATA[<span style="font-weight: bold;" class="mycode_b">FastCGI page caching can let Nginx answer an anonymous request without running PHP. Verify both cache hits and exclusions before relying on it: private or personalized responses must not be served from a shared anonymous cache.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Scope</span>: We tested an existing WordPress FastCGI cache in aaPanel Free 8.0.7 with Nginx and PHP 8.5. This guide verifies the existing setup; it does not claim we installed a new cache or fully audited every authenticated workflow.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">1. Identify the cache layer</span><br />
Page caching stores a rendered HTTP response. OPcache stores compiled PHP bytecode; Redis object caching stores application objects. They solve different problems. A page-cache HIT may avoid PHP, so it cannot be used to measure PHP or SQL tuning alone.<br />
Inspect the website's Nginx configuration and included PHP handler. Our WordPress site included enable-php-85-wpfastcgi.conf and used a WORDPRESS FastCGI cache zone.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">2. Read the rules before testing</span><br />
Our existing rules bypassed POST requests, nonempty query strings, WordPress administrative/login-related paths and cookies matching WordPress login or password-protected content. These rules must be checked against the actual applications on the site.<br />
The handler used both:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>fastcgi_cache_bypass &#36;skip_cache;
fastcgi_no_cache &#36;skip_cache;</code></div></div>The first controls whether an existing cached response is used; the second controls whether the upstream response is stored. A correct private-response policy needs both aspects considered. Do not copy an incomplete location block or override application Set-Cookie behavior blindly.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">3. Inspect cache headers</span><br />
Our configuration exposed Nginx-Cache with the upstream cache status. Your site may use another header or expose none.<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>curl -sS -D - -o /dev/null https://example.com/</code></div></div>Replace the hostname. Read the HTTP status and cache header. HIT means a stored response was served. MISS means the cache did not supply an entry; an eligible response may subsequently be stored. BYPASS means the rules skipped reading the cache. Absence of a header does not prove caching is disabled.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">4. Repeat an anonymous request</span><br />
We sent five sequential requests to the same lab homepage over loopback HTTPS with certificate validation. All five returned HTTP 200 and HIT; median time to first byte was approximately 58ms.<br />
The page was already cached. We did not claim to demonstrate a fresh MISS-to-HIT transition, clear the whole cache or measure maximum throughput. Five requests are a small diagnostic sample.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">5. Check query and cookie exclusions</span><br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>curl -sS -D - -o /dev/null 'https://example.com/?cache_check=1'
curl -sS -D - -o /dev/null &#92;
  -H 'Cookie: wordpress_logged_in_lab=synthetic-test' https://example.com/
curl -sS -D - -o /dev/null https://example.com/wp-login.php</code></div></div>The query-string request, synthetic login-cookie request and login page each returned HTTP 200 and BYPASS in our lab. The synthetic cookie contains no real login token; it tests the configured cookie-name rule only. It does not authenticate a user or prove actual account privacy end to end.<br />
<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://adminsforum.com/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=293" target="_blank" title="">cache-performance-20261014.jpg</a> (Size: 105.63 KB / Downloads: 7)
<!-- end: postbit_attachments_attachment --><br />
<span style="font-style: italic;" class="mycode_i">AdminsForum formatted transcript of the original checks, not a screenshot.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">6. Test actual personalized workflows before rollout</span><br />
With a controlled test account, verify that logged-in pages, shopping carts, checkout, account settings and password-protected content are excluded. Test two separate sessions and inspect rendered content, not just a fast response. Review authorization, cookies, cache keys and application-specific paths.<br />
Our short check did not perform these authenticated workflows. Treat them as required verification before enabling shared page caching for a real personalized application.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">7. Verify freshness and controlled purging</span><br />
After updating a public test page, confirm when anonymous visitors receive the new content. Purge the relevant cache entry using your supported administration workflow and verify the result. Do not expose an unrestricted public purge endpoint.<br />
Our existing configuration restricted its purge location to loopback. We did not weaken that restriction or perform a global purge for this guide. Cache lifetime and invalidation should match how frequently your pages change.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">8. Troubleshoot a persistent BYPASS or stale page</span><br />
Check whether a query string, cookie, request method or path matches an exclusion. Inspect the effective Nginx configuration and logs. If a public page bypasses unexpectedly, identify the cause rather than deleting all exclusions.<br />
For stale content, check the correct hostname, cache key, purge method and any additional CDN or browser cache. A browser refresh alone may leave server caches untouched.<br />
Back up before changing rules, validate Nginx syntax, reload and repeat both anonymous and private-response tests. Restore the original rules if personalized content is cached or application behavior changes.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Reference</span>: <a href="https://nginx.org/en/docs/http/ngx_http_fastcgi_module.html#fastcgi_cache_bypass" target="_blank" rel="noopener" class="mycode_url">Official Nginx FastCGI cache controls</a>.]]></description>
			<content:encoded><![CDATA[<span style="font-weight: bold;" class="mycode_b">FastCGI page caching can let Nginx answer an anonymous request without running PHP. Verify both cache hits and exclusions before relying on it: private or personalized responses must not be served from a shared anonymous cache.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Scope</span>: We tested an existing WordPress FastCGI cache in aaPanel Free 8.0.7 with Nginx and PHP 8.5. This guide verifies the existing setup; it does not claim we installed a new cache or fully audited every authenticated workflow.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">1. Identify the cache layer</span><br />
Page caching stores a rendered HTTP response. OPcache stores compiled PHP bytecode; Redis object caching stores application objects. They solve different problems. A page-cache HIT may avoid PHP, so it cannot be used to measure PHP or SQL tuning alone.<br />
Inspect the website's Nginx configuration and included PHP handler. Our WordPress site included enable-php-85-wpfastcgi.conf and used a WORDPRESS FastCGI cache zone.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">2. Read the rules before testing</span><br />
Our existing rules bypassed POST requests, nonempty query strings, WordPress administrative/login-related paths and cookies matching WordPress login or password-protected content. These rules must be checked against the actual applications on the site.<br />
The handler used both:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>fastcgi_cache_bypass &#36;skip_cache;
fastcgi_no_cache &#36;skip_cache;</code></div></div>The first controls whether an existing cached response is used; the second controls whether the upstream response is stored. A correct private-response policy needs both aspects considered. Do not copy an incomplete location block or override application Set-Cookie behavior blindly.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">3. Inspect cache headers</span><br />
Our configuration exposed Nginx-Cache with the upstream cache status. Your site may use another header or expose none.<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>curl -sS -D - -o /dev/null https://example.com/</code></div></div>Replace the hostname. Read the HTTP status and cache header. HIT means a stored response was served. MISS means the cache did not supply an entry; an eligible response may subsequently be stored. BYPASS means the rules skipped reading the cache. Absence of a header does not prove caching is disabled.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">4. Repeat an anonymous request</span><br />
We sent five sequential requests to the same lab homepage over loopback HTTPS with certificate validation. All five returned HTTP 200 and HIT; median time to first byte was approximately 58ms.<br />
The page was already cached. We did not claim to demonstrate a fresh MISS-to-HIT transition, clear the whole cache or measure maximum throughput. Five requests are a small diagnostic sample.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">5. Check query and cookie exclusions</span><br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>curl -sS -D - -o /dev/null 'https://example.com/?cache_check=1'
curl -sS -D - -o /dev/null &#92;
  -H 'Cookie: wordpress_logged_in_lab=synthetic-test' https://example.com/
curl -sS -D - -o /dev/null https://example.com/wp-login.php</code></div></div>The query-string request, synthetic login-cookie request and login page each returned HTTP 200 and BYPASS in our lab. The synthetic cookie contains no real login token; it tests the configured cookie-name rule only. It does not authenticate a user or prove actual account privacy end to end.<br />
<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://adminsforum.com/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=293" target="_blank" title="">cache-performance-20261014.jpg</a> (Size: 105.63 KB / Downloads: 7)
<!-- end: postbit_attachments_attachment --><br />
<span style="font-style: italic;" class="mycode_i">AdminsForum formatted transcript of the original checks, not a screenshot.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">6. Test actual personalized workflows before rollout</span><br />
With a controlled test account, verify that logged-in pages, shopping carts, checkout, account settings and password-protected content are excluded. Test two separate sessions and inspect rendered content, not just a fast response. Review authorization, cookies, cache keys and application-specific paths.<br />
Our short check did not perform these authenticated workflows. Treat them as required verification before enabling shared page caching for a real personalized application.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">7. Verify freshness and controlled purging</span><br />
After updating a public test page, confirm when anonymous visitors receive the new content. Purge the relevant cache entry using your supported administration workflow and verify the result. Do not expose an unrestricted public purge endpoint.<br />
Our existing configuration restricted its purge location to loopback. We did not weaken that restriction or perform a global purge for this guide. Cache lifetime and invalidation should match how frequently your pages change.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">8. Troubleshoot a persistent BYPASS or stale page</span><br />
Check whether a query string, cookie, request method or path matches an exclusion. Inspect the effective Nginx configuration and logs. If a public page bypasses unexpectedly, identify the cause rather than deleting all exclusions.<br />
For stale content, check the correct hostname, cache key, purge method and any additional CDN or browser cache. A browser refresh alone may leave server caches untouched.<br />
Back up before changing rules, validate Nginx syntax, reload and repeat both anonymous and private-response tests. Restore the original rules if personalized content is cached or application behavior changes.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Reference</span>: <a href="https://nginx.org/en/docs/http/ngx_http_fastcgi_module.html#fastcgi_cache_bypass" target="_blank" rel="noopener" class="mycode_url">Official Nginx FastCGI cache controls</a>.]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[Run a Controlled WordPress Load Test on an aaPanel VPS]]></title>
			<link>https://adminsforum.com/Thread-guide-run-a-controlled-wordpress-load-test-on-an-aapanel-vps</link>
			<pubDate>Wed, 14 Oct 2026 20:22:42 +0800</pubDate>
			<dc:creator><![CDATA[<a href="https://adminsforum.com/member.php?action=profile&uid=2">DEV</a>]]></dc:creator>
			<guid isPermaLink="false">https://adminsforum.com/Thread-guide-run-a-controlled-wordpress-load-test-on-an-aapanel-vps</guid>
			<description><![CDATA[<span style="font-weight: bold;" class="mycode_b">A controlled load test helps compare one workload at several concurrency levels. It does not prove how many visitors an entire website can support. This guide records a bounded external-client test and explains the measurements still needed for a production decision.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Environment</span>: two-CPU shared Ubuntu VPS, aaPanel Free 8.0.7, Nginx, WordPress and PHP 8.5. The tested FPM pool had a maximum of eight children and OPcache enabled. Existing FastCGI cache rules bypassed query-string requests. The generator ran on a separate Windows machine.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">1. Choose a safe target and scope</span><br />
Use a lab server you control. Choose a harmless GET endpoint and a finite request count. Exclude actions that create accounts, orders, mail or database writes. Agree a maintenance window for production testing and monitor other services sharing the machine.<br />
Our target was the WordPress homepage with a unique test query on each request. We verified the query rule returned Nginx-Cache: BYPASS before the run. This measures an uncached homepage path, not checkout, search, login or an entire browsing session.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">2. Record the baseline and configuration</span><br />
Write down PHP version, FPM worker limits, cache states, database version and application version/plugins. Keep those constant between phases. Record server memory, swap and CPU before testing; arrange an interval resource sampler if you need to locate saturation.<br />
This run did not collect a simultaneous server resource trace. We therefore report response results without claiming whether PHP, CPU or SQL limited throughput.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">3. Set explicit limits and stop conditions</span><br />
Our client used 40 requests per phase at concurrency 2, then 4, then 8. Each curl request had a 15-second maximum time, used normal HTTPS certificate validation and performed no retries. Later phases would stop if any request failed or the sample p95 time to first byte exceeded five seconds.<br />
A short request count bounds the total work but can miss sustained bottlenecks. Avoid unbounded loops and high concurrency simply to generate a dramatic result. Watch availability and stop manually if important services degrade.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">4. Understand the client measurements</span><br />
A single request example is:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>curl.exe -sS --max-time 15 -o NUL &#92;
  -w "%{http_code} %{time_starttransfer} %{time_total}" &#92;
  "https://example.com/?load_test=unique_value"</code></div></div>This is a Windows example: NUL discards the body. Use /dev/null on Linux. Replace the hostname and make each test query unique if your verified rules use query strings to bypass caching. Do not disable certificate checks.<br />
Time to first byte includes client-side connection and network effects as well as server waiting. Total time includes body transfer. An HTTP 200 alone does not prove the content is correct; separately inspect a sample response for application errors.<br />
We used a bounded client worker pool to run the finite requests concurrently, waited for each phase to complete, then calculated its results. These are closed-loop tests: workers submit another request after the previous one finishes, rather than maintaining a fixed independent arrival rate.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">5. Read the actual results</span><br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://adminsforum.com/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=294" target="_blank" title="">load-performance-20261014.jpg</a> (Size: 124.63 KB / Downloads: 6)
<!-- end: postbit_attachments_attachment --><br />
<span style="font-style: italic;" class="mycode_i">AdminsForum formatted transcript of the original client test, not a screenshot.</span><br />
<ul class="mycode_list"><li>Concurrency 2: 40/40 HTTP 200; median TTFB 295ms; sample p95 334ms; phase elapsed 8.46 seconds; about 4.73 completed requests/second.<br />
</li>
<li>Concurrency 4: 40/40 HTTP 200; median TTFB 334ms; sample p95 431ms; phase elapsed 4.91 seconds; about 8.15 completed requests/second.<br />
</li>
<li>Concurrency 8: 40/40 HTTP 200; median TTFB 377ms; sample p95 609ms; phase elapsed 2.88 seconds; about 13.87 completed requests/second.<br />
</li>
</ul>
All 120 requests completed with HTTP 200 and no client request failure. Concurrency increased the observed completion rate and latency. The phases were short, were not repeated in randomized order, and did not isolate cache-warmth or resource effects. Do not interpret the numbers as a guaranteed sustained rate or maximum visitors.<br />
The sample p95 is a nearest-rank estimate from forty observations, not a precise long-term tail metric. Network variation and shared-server activity can affect it.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">6. Add resource evidence before tuning</span><br />
For a repeat test, sample CPU utilization, iowait, free memory, swap activity, FPM queues and active database work throughout the phases. Private FPM status and slow logs can help locate queueing. Keep diagnostic endpoints private and redact logs before sharing.<br />
If latency rises with CPU saturation, more FPM workers may worsen contention. If queries dominate, inspect SQL plans. If the client itself is saturated, the generator may be the limit. These conclusions need measurements; this run does not establish them.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">7. Repeat a representative workload</span><br />
Use the same test after one targeted change. Include realistic page mixes, authenticated workflows under controlled accounts, writes in a disposable environment and sustained runs appropriate to your service. Measure errors and content correctness alongside latency.<br />
Test anonymous cache HIT and uncached behavior separately. A fast cached homepage cannot prove that private or dynamic pages perform well. Never increase a pool limit solely because a cached benchmark looks good.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">8. Preserve results and restore experimental changes</span><br />
Keep raw request outcomes, timing definitions and configuration snapshots. Remove only your own disposable fixtures when no longer needed. Retain rollback copies for settings changed during earlier tests. This load run itself changed no server configuration or website content.<br />
<br />
Use <a href="https://adminsforum.com/Thread-guide-measure-aapanel-website-performance-before-tuning-your-server" target="_blank" rel="noopener" class="mycode_url">the baseline guide</a>, the measured PHP-FPM pool guide and the database index test as a repeatable series. Present the workload and its limits whenever quoting performance numbers.]]></description>
			<content:encoded><![CDATA[<span style="font-weight: bold;" class="mycode_b">A controlled load test helps compare one workload at several concurrency levels. It does not prove how many visitors an entire website can support. This guide records a bounded external-client test and explains the measurements still needed for a production decision.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Environment</span>: two-CPU shared Ubuntu VPS, aaPanel Free 8.0.7, Nginx, WordPress and PHP 8.5. The tested FPM pool had a maximum of eight children and OPcache enabled. Existing FastCGI cache rules bypassed query-string requests. The generator ran on a separate Windows machine.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">1. Choose a safe target and scope</span><br />
Use a lab server you control. Choose a harmless GET endpoint and a finite request count. Exclude actions that create accounts, orders, mail or database writes. Agree a maintenance window for production testing and monitor other services sharing the machine.<br />
Our target was the WordPress homepage with a unique test query on each request. We verified the query rule returned Nginx-Cache: BYPASS before the run. This measures an uncached homepage path, not checkout, search, login or an entire browsing session.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">2. Record the baseline and configuration</span><br />
Write down PHP version, FPM worker limits, cache states, database version and application version/plugins. Keep those constant between phases. Record server memory, swap and CPU before testing; arrange an interval resource sampler if you need to locate saturation.<br />
This run did not collect a simultaneous server resource trace. We therefore report response results without claiming whether PHP, CPU or SQL limited throughput.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">3. Set explicit limits and stop conditions</span><br />
Our client used 40 requests per phase at concurrency 2, then 4, then 8. Each curl request had a 15-second maximum time, used normal HTTPS certificate validation and performed no retries. Later phases would stop if any request failed or the sample p95 time to first byte exceeded five seconds.<br />
A short request count bounds the total work but can miss sustained bottlenecks. Avoid unbounded loops and high concurrency simply to generate a dramatic result. Watch availability and stop manually if important services degrade.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">4. Understand the client measurements</span><br />
A single request example is:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>curl.exe -sS --max-time 15 -o NUL &#92;
  -w "%{http_code} %{time_starttransfer} %{time_total}" &#92;
  "https://example.com/?load_test=unique_value"</code></div></div>This is a Windows example: NUL discards the body. Use /dev/null on Linux. Replace the hostname and make each test query unique if your verified rules use query strings to bypass caching. Do not disable certificate checks.<br />
Time to first byte includes client-side connection and network effects as well as server waiting. Total time includes body transfer. An HTTP 200 alone does not prove the content is correct; separately inspect a sample response for application errors.<br />
We used a bounded client worker pool to run the finite requests concurrently, waited for each phase to complete, then calculated its results. These are closed-loop tests: workers submit another request after the previous one finishes, rather than maintaining a fixed independent arrival rate.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">5. Read the actual results</span><br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://adminsforum.com/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=294" target="_blank" title="">load-performance-20261014.jpg</a> (Size: 124.63 KB / Downloads: 6)
<!-- end: postbit_attachments_attachment --><br />
<span style="font-style: italic;" class="mycode_i">AdminsForum formatted transcript of the original client test, not a screenshot.</span><br />
<ul class="mycode_list"><li>Concurrency 2: 40/40 HTTP 200; median TTFB 295ms; sample p95 334ms; phase elapsed 8.46 seconds; about 4.73 completed requests/second.<br />
</li>
<li>Concurrency 4: 40/40 HTTP 200; median TTFB 334ms; sample p95 431ms; phase elapsed 4.91 seconds; about 8.15 completed requests/second.<br />
</li>
<li>Concurrency 8: 40/40 HTTP 200; median TTFB 377ms; sample p95 609ms; phase elapsed 2.88 seconds; about 13.87 completed requests/second.<br />
</li>
</ul>
All 120 requests completed with HTTP 200 and no client request failure. Concurrency increased the observed completion rate and latency. The phases were short, were not repeated in randomized order, and did not isolate cache-warmth or resource effects. Do not interpret the numbers as a guaranteed sustained rate or maximum visitors.<br />
The sample p95 is a nearest-rank estimate from forty observations, not a precise long-term tail metric. Network variation and shared-server activity can affect it.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">6. Add resource evidence before tuning</span><br />
For a repeat test, sample CPU utilization, iowait, free memory, swap activity, FPM queues and active database work throughout the phases. Private FPM status and slow logs can help locate queueing. Keep diagnostic endpoints private and redact logs before sharing.<br />
If latency rises with CPU saturation, more FPM workers may worsen contention. If queries dominate, inspect SQL plans. If the client itself is saturated, the generator may be the limit. These conclusions need measurements; this run does not establish them.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">7. Repeat a representative workload</span><br />
Use the same test after one targeted change. Include realistic page mixes, authenticated workflows under controlled accounts, writes in a disposable environment and sustained runs appropriate to your service. Measure errors and content correctness alongside latency.<br />
Test anonymous cache HIT and uncached behavior separately. A fast cached homepage cannot prove that private or dynamic pages perform well. Never increase a pool limit solely because a cached benchmark looks good.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">8. Preserve results and restore experimental changes</span><br />
Keep raw request outcomes, timing definitions and configuration snapshots. Remove only your own disposable fixtures when no longer needed. Retain rollback copies for settings changed during earlier tests. This load run itself changed no server configuration or website content.<br />
<br />
Use <a href="https://adminsforum.com/Thread-guide-measure-aapanel-website-performance-before-tuning-your-server" target="_blank" rel="noopener" class="mycode_url">the baseline guide</a>, the measured PHP-FPM pool guide and the database index test as a repeatable series. Present the workload and its limits whenever quoting performance numbers.]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[Test Nginx Performance and Gzip Compression in aaPanel]]></title>
			<link>https://adminsforum.com/Thread-guide-test-nginx-performance-and-gzip-compression-in-aapanel</link>
			<pubDate>Wed, 14 Oct 2026 20:21:21 +0800</pubDate>
			<dc:creator><![CDATA[<a href="https://adminsforum.com/member.php?action=profile&uid=2">DEV</a>]]></dc:creator>
			<guid isPermaLink="false">https://adminsforum.com/Thread-guide-test-nginx-performance-and-gzip-compression-in-aapanel</guid>
			<description><![CDATA[<span style="font-weight: bold;" class="mycode_b">Nginx tuning should start with measurements, not a list of larger limits. This guide checks worker settings and gzip behavior, demonstrates a real compression tradeoff, and explains when to restore a change.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Tested</span>: aaPanel Free 8.0.7, Nginx 1.30.5 and Ubuntu 22.04.5 on the shared two-CPU lab VPS. No production configuration was changed.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">1. Inspect the active configuration</span><br />
Open App Store → Installed → Nginx → Setting and inspect its configuration. Paths and interface labels can differ by release. Our global file was /www/server/nginx/conf/nginx.conf; website configurations were included separately.<br />
A configuration dump can help identify includes:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/www/server/nginx/sbin/nginx -T</code></div></div>Keep the output private because it can reveal hostnames, paths and configuration details. Identify which file owns the directive before editing it.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">2. Review workers and connection limits</span><br />
Our server already used worker_processes auto and worker_connections 51200. We did not increase either. More permitted connections do not mean more requests per second: upstream connections, open-file limits, PHP workers, database work and CPU also constrain throughput.<br />
Inspect actual worker count, active connections, system file limits and errors during representative traffic. Raise a limit only if it is the observed bottleneck and the server has resources to support it. Avoid treating a high worker_connections value as a visitor-capacity guarantee.<br />
Keepalive settings also need workload evidence. Shorter idle time can reduce held connections but increase connection setup; longer idle time can retain resources. We left the existing 60-second setting unchanged.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">3. Back up before a compression experiment</span><br />
Make a private copy of the active file. Our baseline already had gzip enabled, a 1KiB minimum length, compression level 5 and gzip_vary on. Its gzip_types included text, JavaScript, CSS, JSON and several image types.<br />
Inspect response headers and actual transfer sizes before changing MIME types. Many image formats contain compressed data, but the effect of an additional gzip layer depends on the particular file.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">4. Test text and image responses</span><br />
We created an original 24,500-byte text fixture in the lab website and used an existing 3,113-byte WordPress PNG. We requested gzip explicitly over loopback HTTPS with normal certificate validation.<br />
A basic header check is:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>curl -sS -D - -o /dev/null &#92;
  -H 'Accept-Encoding: gzip' https://example.com/test.txt</code></div></div>Use your own hostname and a safe test file. Inspect Content-Encoding, Vary and HTTP status. To compare raw encoded bytes, save the response body without automatic decompression. curl --compressed instead requests supported compression and decompresses the body, so do not confuse decoded size with wire size.<br />
Our test compared the baseline against removing JPEG/GIF/PNG from gzip_types. It did not change the compression level or worker limits.<br />
<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://adminsforum.com/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=292" target="_blank" title="">nginx-performance-20261014.jpg</a> (Size: 116.44 KB / Downloads: 4)
<!-- end: postbit_attachments_attachment --><br />
<span style="font-style: italic;" class="mycode_i">AdminsForum formatted transcript of the experiment, not a screenshot.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">5. Interpret the evidence</span><ul class="mycode_list"><li>Text: gzip was used in both phases; the body transferred was 156 bytes and decoded to 24,500 bytes.<br />
</li>
<li>PNG before: gzip body 2,487 bytes, decoded to 3,113 bytes.<br />
</li>
<li>PNG after: no gzip; body transferred 3,113 bytes.<br />
</li>
<li>All responses returned HTTP 200, and decoded content hashes matched.<br />
</li>
</ul>
Removing image compression increased transfer size for this particular small PNG. We did not measure CPU savings, so we did not claim the change improved performance. These body sizes exclude headers and transport overhead; one file does not represent an entire image library.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">6. Validate, reload and restore when appropriate</span><br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/www/server/nginx/sbin/nginx -t
/www/server/nginx/sbin/nginx -s reload</code></div></div>Validation passed for the experiment. Because we had no demonstrated overall benefit, we restored the original configuration, validated it again and reloaded Nginx. A successful reload is not an application test: check important pages, TLS, redirects, upstream errors and logs afterwards.<br />
Do not keep an optimization solely because it looks conventional. Record the tradeoff and test enough representative content to support a decision.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">7. Apply this method to a real high-traffic site</span><br />
Collect response latency, transfer sizes, CPU, connection counts and upstream timing over comparable windows. Separate cached and uncached requests. Change one variable at a time and repeat the same workload. Keep a rollback copy and stop if errors or resource pressure increase.<br />
A compression change cannot repair slow SQL or exhausted PHP workers. Use the database and PHP-FPM guides alongside this test, then investigate the bottleneck actually observed.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Reference</span>: <a href="https://nginx.org/en/docs/http/ngx_http_gzip_module.html" target="_blank" rel="noopener" class="mycode_url">Official Nginx gzip module documentation</a>.]]></description>
			<content:encoded><![CDATA[<span style="font-weight: bold;" class="mycode_b">Nginx tuning should start with measurements, not a list of larger limits. This guide checks worker settings and gzip behavior, demonstrates a real compression tradeoff, and explains when to restore a change.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Tested</span>: aaPanel Free 8.0.7, Nginx 1.30.5 and Ubuntu 22.04.5 on the shared two-CPU lab VPS. No production configuration was changed.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">1. Inspect the active configuration</span><br />
Open App Store → Installed → Nginx → Setting and inspect its configuration. Paths and interface labels can differ by release. Our global file was /www/server/nginx/conf/nginx.conf; website configurations were included separately.<br />
A configuration dump can help identify includes:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/www/server/nginx/sbin/nginx -T</code></div></div>Keep the output private because it can reveal hostnames, paths and configuration details. Identify which file owns the directive before editing it.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">2. Review workers and connection limits</span><br />
Our server already used worker_processes auto and worker_connections 51200. We did not increase either. More permitted connections do not mean more requests per second: upstream connections, open-file limits, PHP workers, database work and CPU also constrain throughput.<br />
Inspect actual worker count, active connections, system file limits and errors during representative traffic. Raise a limit only if it is the observed bottleneck and the server has resources to support it. Avoid treating a high worker_connections value as a visitor-capacity guarantee.<br />
Keepalive settings also need workload evidence. Shorter idle time can reduce held connections but increase connection setup; longer idle time can retain resources. We left the existing 60-second setting unchanged.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">3. Back up before a compression experiment</span><br />
Make a private copy of the active file. Our baseline already had gzip enabled, a 1KiB minimum length, compression level 5 and gzip_vary on. Its gzip_types included text, JavaScript, CSS, JSON and several image types.<br />
Inspect response headers and actual transfer sizes before changing MIME types. Many image formats contain compressed data, but the effect of an additional gzip layer depends on the particular file.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">4. Test text and image responses</span><br />
We created an original 24,500-byte text fixture in the lab website and used an existing 3,113-byte WordPress PNG. We requested gzip explicitly over loopback HTTPS with normal certificate validation.<br />
A basic header check is:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>curl -sS -D - -o /dev/null &#92;
  -H 'Accept-Encoding: gzip' https://example.com/test.txt</code></div></div>Use your own hostname and a safe test file. Inspect Content-Encoding, Vary and HTTP status. To compare raw encoded bytes, save the response body without automatic decompression. curl --compressed instead requests supported compression and decompresses the body, so do not confuse decoded size with wire size.<br />
Our test compared the baseline against removing JPEG/GIF/PNG from gzip_types. It did not change the compression level or worker limits.<br />
<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://adminsforum.com/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=292" target="_blank" title="">nginx-performance-20261014.jpg</a> (Size: 116.44 KB / Downloads: 4)
<!-- end: postbit_attachments_attachment --><br />
<span style="font-style: italic;" class="mycode_i">AdminsForum formatted transcript of the experiment, not a screenshot.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">5. Interpret the evidence</span><ul class="mycode_list"><li>Text: gzip was used in both phases; the body transferred was 156 bytes and decoded to 24,500 bytes.<br />
</li>
<li>PNG before: gzip body 2,487 bytes, decoded to 3,113 bytes.<br />
</li>
<li>PNG after: no gzip; body transferred 3,113 bytes.<br />
</li>
<li>All responses returned HTTP 200, and decoded content hashes matched.<br />
</li>
</ul>
Removing image compression increased transfer size for this particular small PNG. We did not measure CPU savings, so we did not claim the change improved performance. These body sizes exclude headers and transport overhead; one file does not represent an entire image library.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">6. Validate, reload and restore when appropriate</span><br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/www/server/nginx/sbin/nginx -t
/www/server/nginx/sbin/nginx -s reload</code></div></div>Validation passed for the experiment. Because we had no demonstrated overall benefit, we restored the original configuration, validated it again and reloaded Nginx. A successful reload is not an application test: check important pages, TLS, redirects, upstream errors and logs afterwards.<br />
Do not keep an optimization solely because it looks conventional. Record the tradeoff and test enough representative content to support a decision.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">7. Apply this method to a real high-traffic site</span><br />
Collect response latency, transfer sizes, CPU, connection counts and upstream timing over comparable windows. Separate cached and uncached requests. Change one variable at a time and repeat the same workload. Keep a rollback copy and stop if errors or resource pressure increase.<br />
A compression change cannot repair slow SQL or exhausted PHP workers. Use the database and PHP-FPM guides alongside this test, then investigate the bottleneck actually observed.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Reference</span>: <a href="https://nginx.org/en/docs/http/ngx_http_gzip_module.html" target="_blank" rel="noopener" class="mycode_url">Official Nginx gzip module documentation</a>.]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[Measure aaPanel Website Performance Before Tuning Your Server]]></title>
			<link>https://adminsforum.com/Thread-guide-measure-aapanel-website-performance-before-tuning-your-server</link>
			<pubDate>Wed, 14 Oct 2026 18:32:23 +0800</pubDate>
			<dc:creator><![CDATA[<a href="https://adminsforum.com/member.php?action=profile&uid=2">DEV</a>]]></dc:creator>
			<guid isPermaLink="false">https://adminsforum.com/Thread-guide-measure-aapanel-website-performance-before-tuning-your-server</guid>
			<description><![CDATA[<span style="font-weight: bold;" class="mycode_b">Before fine-tuning PHP-FPM, SQL or Nginx for a busy website, record a repeatable baseline. Measure response time and errors alongside server resources so later changes can be compared with the same workload.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Series introduction:</span> This lesson records low-load observations on our aaPanel Free 8.0.7 lab. It does not stress-test the server, prove maximum visitor capacity or recommend universal tuning values. PHP-FPM, OPcache, database and caching tuning belong in separate measured lessons.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">1. Describe the server and workload</span><br />
Our lab VPS has two available CPUs, 3.8Gi RAM and a root/data filesystem reporting 39G capacity. Installed components include Nginx 1.30.5 and MariaDB 10.11.10. The PHP 8.5 CLI binary reports 8.5.11; a CLI version alone is not proof of a website's active PHP-FPM runtime.<br />
<br />
Record the site's selected PHP version in aaPanel, application version, database implementation, URL, authentication state, cache state and any CDN/proxy. A cached anonymous homepage and a logged-in database-heavy request are different workloads. Identify representative pages before deciding what needs tuning.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">2. Check server resources before the sample</span><br />
From SSH:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>nproc
uptime
free -h
df -h /www</code></div></div>Our snapshot showed load averages of 0.32, 0.18 and 0.11, about 2.7Gi available memory, no swap used and 30% disk usage. Those are observations at the time of testing, not permanent operating conditions. See <a href="https://adminsforum.com/Thread-guide-check-vps-disk-space-memory-and-cpu-load-from-ssh" target="_blank" rel="noopener" class="mycode_url">VPS resource checks</a> for interpretation.<br />
<br />
For serious performance work, capture CPU, memory, swap and disk-I/O behavior across the same period as the workload. A single before/after snapshot does not establish peak resource use or rule out brief pressure.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">3. Measure a normal HTTPS request</span><br />
On Linux or macOS, a simple diagnostic command is:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>curl -sS --max-time 20 -o /dev/null   -w 'HTTP=%{http_code} TTFB=%{time_starttransfer} TOTAL=%{time_total}
'   https://your-test-hostname/</code></div></div>On Windows PowerShell use curl.exe and NUL as the output destination. Our test used:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>curl.exe -sS --max-time 20 -o NUL -w "HTTP=%{http_code} TTFB=%{time_starttransfer} TOTAL=%{time_total}" https://your-test-hostname/</code></div></div>Use a hostname you control and keep normal certificate validation enabled. Check the exit code as well as HTTP status; a timeout or TLS failure should count as a failed request rather than disappearing from the results.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">How to read the measurements</span><ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">HTTP status:</span> check whether the response is expected; a fast error is not a fast successful page.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">TTFB:</span> curl's time_starttransfer is elapsed time until the first response byte, including connection-related work. It is not isolated PHP execution time.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Total:</span> time until that single response transfer completes. It does not include rendering a browser page or downloading its complete asset graph.<br />
</li>
</ul>
Validate the response content once as well. Timing a redirect, error template or login page instead of the intended content creates a misleading comparison.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">4. Repeat a small sequential sample</span><br />
We sent ten separate sequential requests to the static lab homepage and ten to the WordPress lab homepage, with a short delay between requests. Each request used a fresh curl process. This was a small diagnostic sample, not a concurrent load test.<br />
<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://adminsforum.com/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=234" target="_blank" title="">performance-baseline-transcript-20261014.jpg</a> (Size: 117.57 KB / Downloads: 9)
<!-- end: postbit_attachments_attachment --><br />
<span style="font-style: italic;" class="mycode_i">Measured lab results, rounded for readability. The image is a formatted transcript rather than a performance guarantee.</span><br />
<br />
Our client-side results were:<ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">Static lab:</span> 10/10 requests returned HTTP 200. Median TTFB 149 ms; slowest total response 240 ms.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">WordPress lab:</span> 10/10 requests returned HTTP 200. Median TTFB 184 ms; slowest total response 424 ms.<br />
</li>
</ul>
These timings include the network path from our client. We did not independently isolate application cache state, PHP time or SQL time in this introductory sample. The ten-request sample is too small to make strong tail-latency or capacity claims.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">5. Record enough information to repeat the test</span><br />
Keep the timestamp and timezone, client location, exact URL, request count, whether requests are sequential/concurrent, timeout, status/exit results and software/configuration versions. Note whether the response came from a CDN/cache or the origin if you have verified that distinction.<br />
<br />
Repeat from the same client using the same workload when comparing a tuning change. Also test the actual user workflow: anonymous content, logged-in pages, search and writes can have very different costs. Do not combine those outcomes into one unexplained average.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">6. Choose the next investigation from evidence</span><ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">PHP worker queues or saturation:</span> inspect PHP-FPM status/slow logs and memory per worker before choosing pm.max_children.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Database-heavy requests:</span> inspect slow queries, query plans, indexes and connection behavior before increasing SQL limits.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Repeated anonymous content:</span> evaluate page caching with appropriate exclusions for private or changing content.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">High I/O wait:</span> investigate storage and query/file access rather than assuming more PHP workers will help.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Slow assets/rendering:</span> measure browser/frontend behavior; an origin response test alone cannot explain it.<br />
</li>
</ul>
A worker limit should account for measured application memory use and RAM reserved for the database, operating system and other services. A database buffer setting must fit the real shared workload. Raising every limit can increase memory pressure instead of throughput.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">7. Plan a controlled comparison</span><br />
Back up the exact configuration before editing it. Change one meaningful setting at a time, validate configuration, repeat the same test and compare latency, error rate and resource use. Define rollback conditions before applying changes.<br />
<br />
A later controlled load test should use an authorized lab target, bounded duration and concurrency, realistic requests and clear stop conditions. Do not infer requests-per-second capacity or supported monthly visitors from the small sequential sample in this guide.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">What this guide verified</span><br />
Two lab URLs each returned HTTP 200 for ten sequential client requests. We recorded client response timings and a server-resource snapshot. No PHP, database or Nginx tuning, stress test or production configuration change was performed.<br />
<br />
<span style="font-style: italic;" class="mycode_i">AdminsForum performance series, lesson 1: baseline measurement. Later lessons will compare specific changes against documented workloads.</span>]]></description>
			<content:encoded><![CDATA[<span style="font-weight: bold;" class="mycode_b">Before fine-tuning PHP-FPM, SQL or Nginx for a busy website, record a repeatable baseline. Measure response time and errors alongside server resources so later changes can be compared with the same workload.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Series introduction:</span> This lesson records low-load observations on our aaPanel Free 8.0.7 lab. It does not stress-test the server, prove maximum visitor capacity or recommend universal tuning values. PHP-FPM, OPcache, database and caching tuning belong in separate measured lessons.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">1. Describe the server and workload</span><br />
Our lab VPS has two available CPUs, 3.8Gi RAM and a root/data filesystem reporting 39G capacity. Installed components include Nginx 1.30.5 and MariaDB 10.11.10. The PHP 8.5 CLI binary reports 8.5.11; a CLI version alone is not proof of a website's active PHP-FPM runtime.<br />
<br />
Record the site's selected PHP version in aaPanel, application version, database implementation, URL, authentication state, cache state and any CDN/proxy. A cached anonymous homepage and a logged-in database-heavy request are different workloads. Identify representative pages before deciding what needs tuning.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">2. Check server resources before the sample</span><br />
From SSH:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>nproc
uptime
free -h
df -h /www</code></div></div>Our snapshot showed load averages of 0.32, 0.18 and 0.11, about 2.7Gi available memory, no swap used and 30% disk usage. Those are observations at the time of testing, not permanent operating conditions. See <a href="https://adminsforum.com/Thread-guide-check-vps-disk-space-memory-and-cpu-load-from-ssh" target="_blank" rel="noopener" class="mycode_url">VPS resource checks</a> for interpretation.<br />
<br />
For serious performance work, capture CPU, memory, swap and disk-I/O behavior across the same period as the workload. A single before/after snapshot does not establish peak resource use or rule out brief pressure.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">3. Measure a normal HTTPS request</span><br />
On Linux or macOS, a simple diagnostic command is:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>curl -sS --max-time 20 -o /dev/null   -w 'HTTP=%{http_code} TTFB=%{time_starttransfer} TOTAL=%{time_total}
'   https://your-test-hostname/</code></div></div>On Windows PowerShell use curl.exe and NUL as the output destination. Our test used:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>curl.exe -sS --max-time 20 -o NUL -w "HTTP=%{http_code} TTFB=%{time_starttransfer} TOTAL=%{time_total}" https://your-test-hostname/</code></div></div>Use a hostname you control and keep normal certificate validation enabled. Check the exit code as well as HTTP status; a timeout or TLS failure should count as a failed request rather than disappearing from the results.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">How to read the measurements</span><ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">HTTP status:</span> check whether the response is expected; a fast error is not a fast successful page.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">TTFB:</span> curl's time_starttransfer is elapsed time until the first response byte, including connection-related work. It is not isolated PHP execution time.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Total:</span> time until that single response transfer completes. It does not include rendering a browser page or downloading its complete asset graph.<br />
</li>
</ul>
Validate the response content once as well. Timing a redirect, error template or login page instead of the intended content creates a misleading comparison.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">4. Repeat a small sequential sample</span><br />
We sent ten separate sequential requests to the static lab homepage and ten to the WordPress lab homepage, with a short delay between requests. Each request used a fresh curl process. This was a small diagnostic sample, not a concurrent load test.<br />
<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://adminsforum.com/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=234" target="_blank" title="">performance-baseline-transcript-20261014.jpg</a> (Size: 117.57 KB / Downloads: 9)
<!-- end: postbit_attachments_attachment --><br />
<span style="font-style: italic;" class="mycode_i">Measured lab results, rounded for readability. The image is a formatted transcript rather than a performance guarantee.</span><br />
<br />
Our client-side results were:<ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">Static lab:</span> 10/10 requests returned HTTP 200. Median TTFB 149 ms; slowest total response 240 ms.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">WordPress lab:</span> 10/10 requests returned HTTP 200. Median TTFB 184 ms; slowest total response 424 ms.<br />
</li>
</ul>
These timings include the network path from our client. We did not independently isolate application cache state, PHP time or SQL time in this introductory sample. The ten-request sample is too small to make strong tail-latency or capacity claims.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">5. Record enough information to repeat the test</span><br />
Keep the timestamp and timezone, client location, exact URL, request count, whether requests are sequential/concurrent, timeout, status/exit results and software/configuration versions. Note whether the response came from a CDN/cache or the origin if you have verified that distinction.<br />
<br />
Repeat from the same client using the same workload when comparing a tuning change. Also test the actual user workflow: anonymous content, logged-in pages, search and writes can have very different costs. Do not combine those outcomes into one unexplained average.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">6. Choose the next investigation from evidence</span><ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">PHP worker queues or saturation:</span> inspect PHP-FPM status/slow logs and memory per worker before choosing pm.max_children.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Database-heavy requests:</span> inspect slow queries, query plans, indexes and connection behavior before increasing SQL limits.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Repeated anonymous content:</span> evaluate page caching with appropriate exclusions for private or changing content.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">High I/O wait:</span> investigate storage and query/file access rather than assuming more PHP workers will help.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Slow assets/rendering:</span> measure browser/frontend behavior; an origin response test alone cannot explain it.<br />
</li>
</ul>
A worker limit should account for measured application memory use and RAM reserved for the database, operating system and other services. A database buffer setting must fit the real shared workload. Raising every limit can increase memory pressure instead of throughput.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">7. Plan a controlled comparison</span><br />
Back up the exact configuration before editing it. Change one meaningful setting at a time, validate configuration, repeat the same test and compare latency, error rate and resource use. Define rollback conditions before applying changes.<br />
<br />
A later controlled load test should use an authorized lab target, bounded duration and concurrency, realistic requests and clear stop conditions. Do not infer requests-per-second capacity or supported monthly visitors from the small sequential sample in this guide.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">What this guide verified</span><br />
Two lab URLs each returned HTTP 200 for ten sequential client requests. We recorded client response timings and a server-resource snapshot. No PHP, database or Nginx tuning, stress test or production configuration change was performed.<br />
<br />
<span style="font-style: italic;" class="mycode_i">AdminsForum performance series, lesson 1: baseline measurement. Later lessons will compare specific changes against documented workloads.</span>]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[Check Nginx, PHP-FPM and MySQL Services in aaPanel via SSH]]></title>
			<link>https://adminsforum.com/Thread-guide-check-nginx-php-fpm-and-mysql-services-in-aapanel-via-ssh</link>
			<pubDate>Wed, 14 Oct 2026 18:14:41 +0800</pubDate>
			<dc:creator><![CDATA[<a href="https://adminsforum.com/member.php?action=profile&uid=2">DEV</a>]]></dc:creator>
			<guid isPermaLink="false">https://adminsforum.com/Thread-guide-check-nginx-php-fpm-and-mysql-services-in-aapanel-via-ssh</guid>
			<description><![CDATA[<span style="font-weight: bold;" class="mycode_b">When a website fails, check the service that handles the failing request. aaPanel-installed Nginx, PHP-FPM and database services can have different names and paths from distribution packages. Inspect the installed service before copying a generic command.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Tested lab:</span> Ubuntu 22.04.5 and aaPanel Free 8.0.7. We checked Nginx, MariaDB under the mysqld service name, and PHP-FPM 8.4/8.5. Nginx and both PHP-FPM configuration tests passed. This lesson tested diagnostic commands without restarting those services.<br />
<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://adminsforum.com/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=232" target="_blank" title="">service-cli-transcript-20261014.jpg</a> (Size: 112.3 KB / Downloads: 5)
<!-- end: postbit_attachments_attachment --><br />
<span style="font-style: italic;" class="mycode_i">Formatted transcript summarizing actual status and configuration-test results from our lab.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">1. Know which component you are checking</span><ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">aaPanel:</span> management dashboard, checked with bt status.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Nginx:</span> handles website requests and proxies dynamic work.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">PHP-FPM:</span> executes PHP for sites configured to use that PHP version.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">MySQL/MariaDB:</span> stores database data; a running database process does not prove an application's credentials work.<br />
</li>
</ul>
Restarting aaPanel does not mean you restarted the website stack. Start with <a href="https://adminsforum.com/Thread-guide-aapanel-cli-overview-what-is-in-the-bt-menu" target="_blank" rel="noopener" class="mycode_url">the bt CLI overview</a> for panel-specific commands.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">2. Find the installed service names</span><br />
From an authorized SSH administrator session:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>ls /etc/init.d/nginx /etc/init.d/mysqld /etc/init.d/php-fpm-*</code></div></div>Our lab had nginx, mysqld, php-fpm-84 and php-fpm-85. If your server uses Apache, another PHP version or a different database setup, inspect those actual names rather than assuming these apply. Missing paths are a reason to verify installation, not to reinstall the server stack blindly.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">3. Check Nginx and validate configuration</span><br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/etc/init.d/nginx status
/www/server/nginx/sbin/nginx -t</code></div></div>The lab's status reported Nginx running. The syntax test reported that /www/server/nginx/conf/nginx.conf was valid and the test was successful.<br />
<br />
A syntax test does not prove the site serves the right content. After a configuration change, check a real HTTP/HTTPS request as well. If validation fails, correct the reported file and line before reloading. Keep a backup of the changed configuration.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Reload versus restart</span><br />
For a reviewed Nginx configuration change, a reload asks the running server to load the new configuration. A restart stops and starts the service and can interrupt requests. The installed init script supports service controls, but this guide did not execute a reload or restart:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/etc/init.d/nginx reload
/etc/init.d/nginx restart</code></div></div>These are operational examples, not commands to run merely because you are reading this guide. Validate first and choose the action that the maintenance requires.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">4. Check the website's PHP-FPM version</span><br />
Confirm the PHP version selected for the website in aaPanel. Our lab has two separate services:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/etc/init.d/php-fpm-84 status
/etc/init.d/php-fpm-85 status</code></div></div>Both reported running. Version-specific configuration tests were:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/www/server/php/84/sbin/php-fpm -t
/www/server/php/85/sbin/php-fpm -t</code></div></div>Both passed. Use the matching path for your installed version. Restarting 8.4 will not repair a site served by 8.5.<br />
<br />
If maintenance genuinely requires restarting the website's PHP-FPM service, its installed service script provides the restart action. For example:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/etc/init.d/php-fpm-85 restart</code></div></div>We did not execute that restart in this lesson. It can affect every site using that PHP-FPM version. Afterwards check service status, recent logs and a meaningful PHP request.<br />
<br />
The shell's php --version can identify a CLI default rather than the PHP-FPM version handling a specific website. Do not use it as the only evidence of the site's runtime.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">5. Check MySQL or MariaDB</span><br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/etc/init.d/mysqld status</code></div></div>Our status output identified <span style="font-weight: bold;" class="mycode_b">MariaDB</span> and active (running), despite the service script being named mysqld. Both naming and installed implementation matter when interpreting commands.<br />
<br />
A database restart can interrupt applications and active connections. Do not restart it as the first response to an authentication or missing-table error. Confirm the actual failure and plan maintenance before using a control action such as:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/etc/init.d/mysqld restart</code></div></div>This restart was not executed for the lesson. Before database maintenance, preserve an appropriate backup and recovery plan. Afterwards verify service readiness and an application connection through its normal secure configuration; do not put database passwords into public command examples.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">6. Verify the website beyond status</span><br />
A running process is one signal, not the whole result. Check:<ul class="mycode_list"><li>The intended service and PHP version.<br />
</li>
<li>Configuration validation after edits.<br />
</li>
<li>The hostname's response with normal certificate validation.<br />
</li>
<li>A dynamic application action and database connection where relevant.<br />
</li>
<li>Recent relevant errors rather than old log entries.<br />
</li>
</ul>
For example, a PHP site can still return 502 when Nginx is running but its configured PHP-FPM socket is wrong. A site can connect to the web server but fail inside the application because its database credentials are incorrect.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Common mistakes</span><ul class="mycode_list"><li>Using system package paths for software installed under /www/server.<br />
</li>
<li>Restarting every service instead of diagnosing the failing component.<br />
</li>
<li>Checking the CLI PHP version instead of the website's configured PHP-FPM version.<br />
</li>
<li>Treating mysqld as proof that the database implementation must be MySQL.<br />
</li>
<li>Reloading invalid configuration or assuming a successful syntax test proves application health.<br />
</li>
<li>Posting raw logs that contain tokens or database credentials.<br />
</li>
</ul>
<br />
<span style="font-weight: bold;" class="mycode_b">What our lab verified</span><br />
Nginx was running and passed its configuration test. MariaDB's mysqld service was active. Both PHP-FPM 8.4 and 8.5 were running and passed configuration tests. Service restart examples were explained, not executed or claimed as tested.<br />
<br />
<span style="font-style: italic;" class="mycode_i">AdminsForum CLI series. The image is a formatted lab-result transcript; use the service names and paths actually installed on your own server.</span>]]></description>
			<content:encoded><![CDATA[<span style="font-weight: bold;" class="mycode_b">When a website fails, check the service that handles the failing request. aaPanel-installed Nginx, PHP-FPM and database services can have different names and paths from distribution packages. Inspect the installed service before copying a generic command.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Tested lab:</span> Ubuntu 22.04.5 and aaPanel Free 8.0.7. We checked Nginx, MariaDB under the mysqld service name, and PHP-FPM 8.4/8.5. Nginx and both PHP-FPM configuration tests passed. This lesson tested diagnostic commands without restarting those services.<br />
<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://adminsforum.com/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=232" target="_blank" title="">service-cli-transcript-20261014.jpg</a> (Size: 112.3 KB / Downloads: 5)
<!-- end: postbit_attachments_attachment --><br />
<span style="font-style: italic;" class="mycode_i">Formatted transcript summarizing actual status and configuration-test results from our lab.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">1. Know which component you are checking</span><ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">aaPanel:</span> management dashboard, checked with bt status.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Nginx:</span> handles website requests and proxies dynamic work.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">PHP-FPM:</span> executes PHP for sites configured to use that PHP version.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">MySQL/MariaDB:</span> stores database data; a running database process does not prove an application's credentials work.<br />
</li>
</ul>
Restarting aaPanel does not mean you restarted the website stack. Start with <a href="https://adminsforum.com/Thread-guide-aapanel-cli-overview-what-is-in-the-bt-menu" target="_blank" rel="noopener" class="mycode_url">the bt CLI overview</a> for panel-specific commands.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">2. Find the installed service names</span><br />
From an authorized SSH administrator session:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>ls /etc/init.d/nginx /etc/init.d/mysqld /etc/init.d/php-fpm-*</code></div></div>Our lab had nginx, mysqld, php-fpm-84 and php-fpm-85. If your server uses Apache, another PHP version or a different database setup, inspect those actual names rather than assuming these apply. Missing paths are a reason to verify installation, not to reinstall the server stack blindly.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">3. Check Nginx and validate configuration</span><br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/etc/init.d/nginx status
/www/server/nginx/sbin/nginx -t</code></div></div>The lab's status reported Nginx running. The syntax test reported that /www/server/nginx/conf/nginx.conf was valid and the test was successful.<br />
<br />
A syntax test does not prove the site serves the right content. After a configuration change, check a real HTTP/HTTPS request as well. If validation fails, correct the reported file and line before reloading. Keep a backup of the changed configuration.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Reload versus restart</span><br />
For a reviewed Nginx configuration change, a reload asks the running server to load the new configuration. A restart stops and starts the service and can interrupt requests. The installed init script supports service controls, but this guide did not execute a reload or restart:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/etc/init.d/nginx reload
/etc/init.d/nginx restart</code></div></div>These are operational examples, not commands to run merely because you are reading this guide. Validate first and choose the action that the maintenance requires.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">4. Check the website's PHP-FPM version</span><br />
Confirm the PHP version selected for the website in aaPanel. Our lab has two separate services:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/etc/init.d/php-fpm-84 status
/etc/init.d/php-fpm-85 status</code></div></div>Both reported running. Version-specific configuration tests were:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/www/server/php/84/sbin/php-fpm -t
/www/server/php/85/sbin/php-fpm -t</code></div></div>Both passed. Use the matching path for your installed version. Restarting 8.4 will not repair a site served by 8.5.<br />
<br />
If maintenance genuinely requires restarting the website's PHP-FPM service, its installed service script provides the restart action. For example:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/etc/init.d/php-fpm-85 restart</code></div></div>We did not execute that restart in this lesson. It can affect every site using that PHP-FPM version. Afterwards check service status, recent logs and a meaningful PHP request.<br />
<br />
The shell's php --version can identify a CLI default rather than the PHP-FPM version handling a specific website. Do not use it as the only evidence of the site's runtime.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">5. Check MySQL or MariaDB</span><br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/etc/init.d/mysqld status</code></div></div>Our status output identified <span style="font-weight: bold;" class="mycode_b">MariaDB</span> and active (running), despite the service script being named mysqld. Both naming and installed implementation matter when interpreting commands.<br />
<br />
A database restart can interrupt applications and active connections. Do not restart it as the first response to an authentication or missing-table error. Confirm the actual failure and plan maintenance before using a control action such as:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/etc/init.d/mysqld restart</code></div></div>This restart was not executed for the lesson. Before database maintenance, preserve an appropriate backup and recovery plan. Afterwards verify service readiness and an application connection through its normal secure configuration; do not put database passwords into public command examples.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">6. Verify the website beyond status</span><br />
A running process is one signal, not the whole result. Check:<ul class="mycode_list"><li>The intended service and PHP version.<br />
</li>
<li>Configuration validation after edits.<br />
</li>
<li>The hostname's response with normal certificate validation.<br />
</li>
<li>A dynamic application action and database connection where relevant.<br />
</li>
<li>Recent relevant errors rather than old log entries.<br />
</li>
</ul>
For example, a PHP site can still return 502 when Nginx is running but its configured PHP-FPM socket is wrong. A site can connect to the web server but fail inside the application because its database credentials are incorrect.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Common mistakes</span><ul class="mycode_list"><li>Using system package paths for software installed under /www/server.<br />
</li>
<li>Restarting every service instead of diagnosing the failing component.<br />
</li>
<li>Checking the CLI PHP version instead of the website's configured PHP-FPM version.<br />
</li>
<li>Treating mysqld as proof that the database implementation must be MySQL.<br />
</li>
<li>Reloading invalid configuration or assuming a successful syntax test proves application health.<br />
</li>
<li>Posting raw logs that contain tokens or database credentials.<br />
</li>
</ul>
<br />
<span style="font-weight: bold;" class="mycode_b">What our lab verified</span><br />
Nginx was running and passed its configuration test. MariaDB's mysqld service was active. Both PHP-FPM 8.4 and 8.5 were running and passed configuration tests. Service restart examples were explained, not executed or claimed as tested.<br />
<br />
<span style="font-style: italic;" class="mycode_i">AdminsForum CLI series. The image is a formatted lab-result transcript; use the service names and paths actually installed on your own server.</span>]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[Manage Python Packages with requirements.txt in aaPanel]]></title>
			<link>https://adminsforum.com/Thread-guide-manage-python-packages-with-requirements-txt-in-aapanel</link>
			<pubDate>Wed, 14 Oct 2026 15:13:52 +0800</pubDate>
			<dc:creator><![CDATA[<a href="https://adminsforum.com/member.php?action=profile&uid=2">DEV</a>]]></dc:creator>
			<guid isPermaLink="false">https://adminsforum.com/Thread-guide-manage-python-packages-with-requirements-txt-in-aapanel</guid>
			<description><![CDATA[<span style="font-weight: bold;" class="mycode_b">requirements.txt records the Python packages an application needs, so you can install them into the correct environment and rebuild the app later. In aaPanel, the key is using the Python executable from the environment selected for that project.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Tested:</span> Our Flask project on aaPanel Free 8.0.7 uses af_python_lab. We also created a separate clean environment, installed the sample's requirements, ran pip check and started the restored app on a different local port. This lesson uses SSH commands for package management; it does not claim a new aaPanel package-management screen exists.<br />
<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://adminsforum.com/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=222" target="_blank" title="">python-requirements-workflow-20261014.jpg</a> (Size: 107.06 KB / Downloads: 5)
<!-- end: postbit_attachments_attachment --><br />
<span style="font-style: italic;" class="mycode_i">Dependency workflow diagram: record, install, check and run the application.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">1. Find the project's actual environment</span><br />
Open <span style="font-weight: bold;" class="mycode_b">Website → Python Project</span>, click the project and inspect <span style="font-weight: bold;" class="mycode_b">Project information → Python Environment</span>. Our selected environment is af_python_lab. Its actual path on this panel version is:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/www/server/pyporject_evn/af_python_lab</code></div></div>Do not assume the path is identical on every release. Verify the executable before installing packages:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/www/server/pyporject_evn/af_python_lab/bin/python --version
/www/server/pyporject_evn/af_python_lab/bin/python -m pip --version</code></div></div>The pip output should point inside that environment. Using python -m pip ties the installer to the chosen interpreter, avoiding a different pip from the shell's PATH.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">2. Create requirements.txt beside the app</span><br />
For our small sample, the project folder is /www/wwwroot/af-flask-lab and the file contains:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>Flask==3.1.3
gunicorn==26.2.0</code></div></div>Each line names a package; == pins the direct version. These are the versions tested in this lab, not an instruction to upgrade every existing app to them. Use your application's known compatible versions and include its real direct dependencies.<br />
<br />
This two-line file does not pin every transitive dependency. Pip will resolve Flask's dependencies when it installs the file. For stronger repeatability, keep a reviewed full version snapshot or the lock file used by your project's dependency workflow.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">3. Understand pip freeze before using it</span><br />
To inspect installed packages in this environment:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/www/server/pyporject_evn/af_python_lab/bin/python -m pip list
/www/server/pyporject_evn/af_python_lab/bin/python -m pip freeze</code></div></div>A freeze snapshot includes installed packages, including packages added by tools or unrelated experiments in that same environment. It does not identify which ones your application directly uses. Review it before using it as your application requirements file, and avoid overwriting an existing reviewed file without keeping a copy.<br />
<br />
AaPanel initialized additional support packages in our project environment. For the tiny Flask restore, we used the application's two direct dependencies instead of blindly copying every installed package.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">4. Install using the intended environment and user</span><br />
If the environment belongs to www, run the installer as that user. Give it a writable cache inside the project rather than broadening permissions on system directories:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>runuser -u www -- /www/server/pyporject_evn/af_python_lab/bin/python   -m pip install --cache-dir /www/wwwroot/af-flask-lab/.pip-cache   -r /www/wwwroot/af-flask-lab/requirements.txt</code></div></div>The project and environment must already have appropriate ownership for www. If the command reports Permission denied, inspect that specific path; do not apply chmod 777 to the environment or the whole website tree.<br />
<br />
Review the requirements file and package source before installation. Do not paste credentials into package URLs that may appear in shell history or logs. For a running application, rehearse dependency changes in a separate environment and plan rollback before changing its live packages.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">5. Check consistency and run the app</span><br />
Use the same interpreter:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/www/server/pyporject_evn/af_python_lab/bin/python -m pip check
/www/server/pyporject_evn/af_python_lab/bin/python -m pip show Flask gunicorn</code></div></div>A successful pip check reports no incompatible or missing declared package requirements. It does not prove your application behavior is correct, so also test startup and a meaningful application request.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">6. Prove the file can rebuild a clean environment</span><br />
Our recovery exercise created a new environment under /www/af-flask-restore-20261014/venv and installed:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/www/af-flask-restore-20261014/venv/bin/python -m pip install   -r /www/af-flask-restore-20261014/af-flask-lab/requirements.txt
/www/af-flask-restore-20261014/venv/bin/python -m pip check</code></div></div>The installation succeeded with Flask 3.1.3 and Gunicorn 26.2.0. Pip check reported <span style="font-weight: bold;" class="mycode_b">No broken requirements found</span>. The rebuilt app returned HTTP 200 and AdminsForum Flask lab on 127.0.0.1:3202, then the temporary test process was stopped. The live website still returned HTTP 200.<br />
<br />
See <a href="https://adminsforum.com/Thread-guide-back-up-and-restore-a-flask-app-on-an-aapanel-server" target="_blank" rel="noopener" class="mycode_url">Back Up and Restore a Flask App</a> for the complete isolated test. A separate environment prevents a failed experiment from replacing the live application's dependencies.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Common errors</span><ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">No module named flask:</span> verify the project uses the same environment in which you installed Flask.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">No matching distribution:</span> check whether the requested package version supports your Python version and platform.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Permission denied:</span> check the installer user, environment ownership and cache directory.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Dependency conflict:</span> read the reported package versions; test compatible pins in a clean environment rather than forcing an inconsistent installation.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Packages install but app fails:</span> check the startup command, environment variables, OS libraries and application logs. Pip cannot configure those for you.<br />
</li>
</ul>
<br />
<span style="font-weight: bold;" class="mycode_b">Completion checklist</span><ul class="mycode_list"><li>Correct project environment identified.<br />
</li>
<li>Application dependency file reviewed and backed up.<br />
</li>
<li>Install and verification use the same interpreter.<br />
</li>
<li>Pip check passes.<br />
</li>
<li>Clean-environment rebuild and application response tested.<br />
</li>
</ul>
<br />
<span style="font-weight: bold;" class="mycode_b">Reference:</span> <a href="https://pip.pypa.io/en/stable/reference/requirements-file-format/" target="_blank" rel="noopener" class="mycode_url">Official pip requirements-file documentation</a> and <a href="https://pip.pypa.io/en/stable/cli/pip_freeze/" target="_blank" rel="noopener" class="mycode_url">pip freeze documentation</a>.<br />
<br />
<span style="font-style: italic;" class="mycode_i">Tested and illustrated by AdminsForum. Diagram summarizes our lab workflow; use paths and versions appropriate to your application.</span>]]></description>
			<content:encoded><![CDATA[<span style="font-weight: bold;" class="mycode_b">requirements.txt records the Python packages an application needs, so you can install them into the correct environment and rebuild the app later. In aaPanel, the key is using the Python executable from the environment selected for that project.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Tested:</span> Our Flask project on aaPanel Free 8.0.7 uses af_python_lab. We also created a separate clean environment, installed the sample's requirements, ran pip check and started the restored app on a different local port. This lesson uses SSH commands for package management; it does not claim a new aaPanel package-management screen exists.<br />
<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://adminsforum.com/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=222" target="_blank" title="">python-requirements-workflow-20261014.jpg</a> (Size: 107.06 KB / Downloads: 5)
<!-- end: postbit_attachments_attachment --><br />
<span style="font-style: italic;" class="mycode_i">Dependency workflow diagram: record, install, check and run the application.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">1. Find the project's actual environment</span><br />
Open <span style="font-weight: bold;" class="mycode_b">Website → Python Project</span>, click the project and inspect <span style="font-weight: bold;" class="mycode_b">Project information → Python Environment</span>. Our selected environment is af_python_lab. Its actual path on this panel version is:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/www/server/pyporject_evn/af_python_lab</code></div></div>Do not assume the path is identical on every release. Verify the executable before installing packages:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/www/server/pyporject_evn/af_python_lab/bin/python --version
/www/server/pyporject_evn/af_python_lab/bin/python -m pip --version</code></div></div>The pip output should point inside that environment. Using python -m pip ties the installer to the chosen interpreter, avoiding a different pip from the shell's PATH.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">2. Create requirements.txt beside the app</span><br />
For our small sample, the project folder is /www/wwwroot/af-flask-lab and the file contains:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>Flask==3.1.3
gunicorn==26.2.0</code></div></div>Each line names a package; == pins the direct version. These are the versions tested in this lab, not an instruction to upgrade every existing app to them. Use your application's known compatible versions and include its real direct dependencies.<br />
<br />
This two-line file does not pin every transitive dependency. Pip will resolve Flask's dependencies when it installs the file. For stronger repeatability, keep a reviewed full version snapshot or the lock file used by your project's dependency workflow.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">3. Understand pip freeze before using it</span><br />
To inspect installed packages in this environment:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/www/server/pyporject_evn/af_python_lab/bin/python -m pip list
/www/server/pyporject_evn/af_python_lab/bin/python -m pip freeze</code></div></div>A freeze snapshot includes installed packages, including packages added by tools or unrelated experiments in that same environment. It does not identify which ones your application directly uses. Review it before using it as your application requirements file, and avoid overwriting an existing reviewed file without keeping a copy.<br />
<br />
AaPanel initialized additional support packages in our project environment. For the tiny Flask restore, we used the application's two direct dependencies instead of blindly copying every installed package.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">4. Install using the intended environment and user</span><br />
If the environment belongs to www, run the installer as that user. Give it a writable cache inside the project rather than broadening permissions on system directories:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>runuser -u www -- /www/server/pyporject_evn/af_python_lab/bin/python   -m pip install --cache-dir /www/wwwroot/af-flask-lab/.pip-cache   -r /www/wwwroot/af-flask-lab/requirements.txt</code></div></div>The project and environment must already have appropriate ownership for www. If the command reports Permission denied, inspect that specific path; do not apply chmod 777 to the environment or the whole website tree.<br />
<br />
Review the requirements file and package source before installation. Do not paste credentials into package URLs that may appear in shell history or logs. For a running application, rehearse dependency changes in a separate environment and plan rollback before changing its live packages.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">5. Check consistency and run the app</span><br />
Use the same interpreter:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/www/server/pyporject_evn/af_python_lab/bin/python -m pip check
/www/server/pyporject_evn/af_python_lab/bin/python -m pip show Flask gunicorn</code></div></div>A successful pip check reports no incompatible or missing declared package requirements. It does not prove your application behavior is correct, so also test startup and a meaningful application request.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">6. Prove the file can rebuild a clean environment</span><br />
Our recovery exercise created a new environment under /www/af-flask-restore-20261014/venv and installed:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/www/af-flask-restore-20261014/venv/bin/python -m pip install   -r /www/af-flask-restore-20261014/af-flask-lab/requirements.txt
/www/af-flask-restore-20261014/venv/bin/python -m pip check</code></div></div>The installation succeeded with Flask 3.1.3 and Gunicorn 26.2.0. Pip check reported <span style="font-weight: bold;" class="mycode_b">No broken requirements found</span>. The rebuilt app returned HTTP 200 and AdminsForum Flask lab on 127.0.0.1:3202, then the temporary test process was stopped. The live website still returned HTTP 200.<br />
<br />
See <a href="https://adminsforum.com/Thread-guide-back-up-and-restore-a-flask-app-on-an-aapanel-server" target="_blank" rel="noopener" class="mycode_url">Back Up and Restore a Flask App</a> for the complete isolated test. A separate environment prevents a failed experiment from replacing the live application's dependencies.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Common errors</span><ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">No module named flask:</span> verify the project uses the same environment in which you installed Flask.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">No matching distribution:</span> check whether the requested package version supports your Python version and platform.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Permission denied:</span> check the installer user, environment ownership and cache directory.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Dependency conflict:</span> read the reported package versions; test compatible pins in a clean environment rather than forcing an inconsistent installation.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Packages install but app fails:</span> check the startup command, environment variables, OS libraries and application logs. Pip cannot configure those for you.<br />
</li>
</ul>
<br />
<span style="font-weight: bold;" class="mycode_b">Completion checklist</span><ul class="mycode_list"><li>Correct project environment identified.<br />
</li>
<li>Application dependency file reviewed and backed up.<br />
</li>
<li>Install and verification use the same interpreter.<br />
</li>
<li>Pip check passes.<br />
</li>
<li>Clean-environment rebuild and application response tested.<br />
</li>
</ul>
<br />
<span style="font-weight: bold;" class="mycode_b">Reference:</span> <a href="https://pip.pypa.io/en/stable/reference/requirements-file-format/" target="_blank" rel="noopener" class="mycode_url">Official pip requirements-file documentation</a> and <a href="https://pip.pypa.io/en/stable/cli/pip_freeze/" target="_blank" rel="noopener" class="mycode_url">pip freeze documentation</a>.<br />
<br />
<span style="font-style: italic;" class="mycode_i">Tested and illustrated by AdminsForum. Diagram summarizes our lab workflow; use paths and versions appropriate to your application.</span>]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[Auto-Start Python and Flask Apps After Reboot in aaPanel]]></title>
			<link>https://adminsforum.com/Thread-guide-auto-start-python-and-flask-apps-after-reboot-in-aapanel</link>
			<pubDate>Wed, 14 Oct 2026 15:06:48 +0800</pubDate>
			<dc:creator><![CDATA[<a href="https://adminsforum.com/member.php?action=profile&uid=2">DEV</a>]]></dc:creator>
			<guid isPermaLink="false">https://adminsforum.com/Thread-guide-auto-start-python-and-flask-apps-after-reboot-in-aapanel</guid>
			<description><![CDATA[<span style="font-weight: bold;" class="mycode_b">A Python project that works today can remain offline after a VPS reboot unless its startup setting is enabled. This guide enables aaPanel's native recovery option and verifies that a Flask app actually returns after a real reboot.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Tested:</span> Ubuntu 22.04.5 and aaPanel Free 8.0.7, with Flask served by Gunicorn through Nginx. Our project is af_flask_lab and listens on 127.0.0.1:3200. Replace these values with your own project. A reboot temporarily interrupts every service on that VPS; use a lab or a planned maintenance window.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">1. Confirm the app is healthy first</span><br />
Complete <a href="https://adminsforum.com/Thread-guide-run-a-flask-app-with-gunicorn-in-aapanel" target="_blank" rel="noopener" class="mycode_url">Run Flask with Gunicorn</a> and, if you need a public hostname, <a href="https://adminsforum.com/Thread-guide-connect-a-flask-app-to-a-domain-and-https-in-aapanel" target="_blank" rel="noopener" class="mycode_url">Connect Flask to a Domain and HTTPS</a>.<br />
<br />
Open Website → Python Project and confirm the application is running. From the VPS terminal:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>curl -i http://127.0.0.1:3200/</code></div></div>You should receive HTTP 200 and your expected application response. Resolve startup errors before testing reboot recovery.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">2. Enable aaPanel's Startup option</span><br />
Click the project name, then open <span style="font-weight: bold;" class="mycode_b">Project information</span>. Locate <span style="font-weight: bold;" class="mycode_b">Startup</span> and tick the checkbox labelled <span style="font-weight: bold;" class="mycode_b">default built-in daemon checks every 120 seconds</span>. Click <span style="font-weight: bold;" class="mycode_b">Save</span>.<br />
<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://adminsforum.com/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=219" target="_blank" title="">python-startup-20261014.jpg</a> (Size: 145.33 KB / Downloads: 4)
<!-- end: postbit_attachments_attachment --><br />
<span style="font-style: italic;" class="mycode_i">Enable the native Startup checkbox for the correct Python project, then save its settings.</span><br />
<br />
This setting tells aaPanel to recover the project when its daemon finds it stopped. It is not an immediate availability guarantee: the panel advertises a 120-second check interval. Keep the same working startup command, environment, project path and non-root user.<br />
<br />
Our command remained:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>gunicorn --workers 1 --bind 127.0.0.1:3200 app:app</code></div></div>The selected environment was af_python_lab and the startup user was www. Do not create a second systemd or cron launcher for the same process while using the native panel daemon; two launchers can compete for the same port.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">3. Recheck HTTP and HTTPS after saving</span><br />
Before rebooting, check localhost and the public hostname again. In our aaPanel 8.0.7 lab, saving the Python project settings regenerated its Nginx configuration and removed the 443 SSL listener. The app still worked on localhost, but public HTTPS selected another site's certificate and failed hostname validation.<br />
<br />
This is a separate configuration problem, not proof that Gunicorn failed. Check <span style="font-weight: bold;" class="mycode_b">SSL</span> and the project's Nginx <span style="font-weight: bold;" class="mycode_b">Config</span> after saving. Back up the configuration before editing it. For this lab we restored the HTTPS listener and the existing certificate references, validated Nginx configuration and reloaded Nginx. We did not issue another certificate.<br />
<br />
The existing certificate paths in this lab were:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/www/server/panel/vhost/cert/af_flask_lab/fullchain.pem
/www/server/panel/vhost/cert/af_flask_lab/privkey.pem</code></div></div>Your project may have different paths. Do not copy another site's certificate or publish private-key contents. Normal HTTPS validation must pass; do not hide the error with curl -k.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">4. Record the current boot before rebooting</span><br />
From the VPS terminal:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>cat /proc/sys/kernel/random/boot_id</code></div></div>Keep this value locally. After the reboot it must differ, proving you tested a new server boot rather than a simple browser refresh.<br />
<br />
Reboot only after your work is saved and you have a working way to reconnect. In a disposable test VPS, a normal reboot command is:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>sudo reboot</code></div></div>An SSH disconnection is expected while the server restarts. A browser tab can also lose its aaPanel session; signing back into the panel is separate from restarting the application.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">5. Verify recovery without manually starting the app</span><br />
Reconnect through SSH after the VPS is reachable. Check:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>cat /proc/sys/kernel/random/boot_id
ss -lntp | grep ':3200'
curl -i http://127.0.0.1:3200/
curl -i https://your-hostname/</code></div></div>Wait for the native daemon's recovery interval if the application has not returned yet. Do not click Start during the automatic recovery test, because that would invalidate the test.<br />
<br />
Our new boot ID differed from the original. Gunicorn returned automatically with a master and worker listening on <span style="font-weight: bold;" class="mycode_b">127.0.0.1:3200</span>. After restoring the SSL listener described above, the public Flask hostname returned <span style="font-weight: bold;" class="mycode_b">HTTP 200</span> with <span style="font-weight: bold;" class="mycode_b">AdminsForum Flask lab</span> and passed certificate validation. Nginx, Postfix and Dovecot were also active.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">6. Inspect logs if recovery fails</span><br />
In the project window, open <span style="font-weight: bold;" class="mycode_b">Project logs</span>. Check the latest startup attempt rather than old messages. For this project, the error log was:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/www/wwwlogs/python/af_flask_lab/error.log</code></div></div>Typical problems include a missing virtual environment, incorrect working directory, missing packages, a port already in use or a startup user unable to read the app files. Confirm that the same command still starts the app in its intended environment before changing the daemon.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Know the limits of this test</span><ul class="mycode_list"><li>A successful reboot test confirms recovery for the tested app and configuration. It does not test database recovery, queues, upload persistence or every application dependency.<br />
</li>
<li>Startup recovery can cause a short period of 502 responses while Nginx is running but Gunicorn has not started yet.<br />
</li>
<li>Because the native daemon recovers stopped projects, temporarily disable Startup when deliberately keeping an app stopped for maintenance; restore it when the maintenance is finished.<br />
</li>
<li>Repeat HTTP and HTTPS checks after later project edits. The SSL configuration issue above was observed on this lab version, not assumed for every aaPanel release.<br />
</li>
</ul>
<br />
<span style="font-weight: bold;" class="mycode_b">Completion checklist</span><ul class="mycode_list"><li>Startup checkbox enabled and saved.<br />
</li>
<li>Correct environment, project path and non-root user retained.<br />
</li>
<li>A real reboot confirmed by a changed boot ID.<br />
</li>
<li>App returned without a manual Start action.<br />
</li>
<li>Local response, public mapping and HTTPS checked separately.<br />
</li>
<li>Any recovery errors explained using current logs.<br />
</li>
</ul>
<br />
<span style="font-style: italic;" class="mycode_i">Tested on the AdminsForum lab VPS. Screenshot and test results are from our own deployment.</span>]]></description>
			<content:encoded><![CDATA[<span style="font-weight: bold;" class="mycode_b">A Python project that works today can remain offline after a VPS reboot unless its startup setting is enabled. This guide enables aaPanel's native recovery option and verifies that a Flask app actually returns after a real reboot.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Tested:</span> Ubuntu 22.04.5 and aaPanel Free 8.0.7, with Flask served by Gunicorn through Nginx. Our project is af_flask_lab and listens on 127.0.0.1:3200. Replace these values with your own project. A reboot temporarily interrupts every service on that VPS; use a lab or a planned maintenance window.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">1. Confirm the app is healthy first</span><br />
Complete <a href="https://adminsforum.com/Thread-guide-run-a-flask-app-with-gunicorn-in-aapanel" target="_blank" rel="noopener" class="mycode_url">Run Flask with Gunicorn</a> and, if you need a public hostname, <a href="https://adminsforum.com/Thread-guide-connect-a-flask-app-to-a-domain-and-https-in-aapanel" target="_blank" rel="noopener" class="mycode_url">Connect Flask to a Domain and HTTPS</a>.<br />
<br />
Open Website → Python Project and confirm the application is running. From the VPS terminal:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>curl -i http://127.0.0.1:3200/</code></div></div>You should receive HTTP 200 and your expected application response. Resolve startup errors before testing reboot recovery.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">2. Enable aaPanel's Startup option</span><br />
Click the project name, then open <span style="font-weight: bold;" class="mycode_b">Project information</span>. Locate <span style="font-weight: bold;" class="mycode_b">Startup</span> and tick the checkbox labelled <span style="font-weight: bold;" class="mycode_b">default built-in daemon checks every 120 seconds</span>. Click <span style="font-weight: bold;" class="mycode_b">Save</span>.<br />
<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://adminsforum.com/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=219" target="_blank" title="">python-startup-20261014.jpg</a> (Size: 145.33 KB / Downloads: 4)
<!-- end: postbit_attachments_attachment --><br />
<span style="font-style: italic;" class="mycode_i">Enable the native Startup checkbox for the correct Python project, then save its settings.</span><br />
<br />
This setting tells aaPanel to recover the project when its daemon finds it stopped. It is not an immediate availability guarantee: the panel advertises a 120-second check interval. Keep the same working startup command, environment, project path and non-root user.<br />
<br />
Our command remained:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>gunicorn --workers 1 --bind 127.0.0.1:3200 app:app</code></div></div>The selected environment was af_python_lab and the startup user was www. Do not create a second systemd or cron launcher for the same process while using the native panel daemon; two launchers can compete for the same port.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">3. Recheck HTTP and HTTPS after saving</span><br />
Before rebooting, check localhost and the public hostname again. In our aaPanel 8.0.7 lab, saving the Python project settings regenerated its Nginx configuration and removed the 443 SSL listener. The app still worked on localhost, but public HTTPS selected another site's certificate and failed hostname validation.<br />
<br />
This is a separate configuration problem, not proof that Gunicorn failed. Check <span style="font-weight: bold;" class="mycode_b">SSL</span> and the project's Nginx <span style="font-weight: bold;" class="mycode_b">Config</span> after saving. Back up the configuration before editing it. For this lab we restored the HTTPS listener and the existing certificate references, validated Nginx configuration and reloaded Nginx. We did not issue another certificate.<br />
<br />
The existing certificate paths in this lab were:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/www/server/panel/vhost/cert/af_flask_lab/fullchain.pem
/www/server/panel/vhost/cert/af_flask_lab/privkey.pem</code></div></div>Your project may have different paths. Do not copy another site's certificate or publish private-key contents. Normal HTTPS validation must pass; do not hide the error with curl -k.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">4. Record the current boot before rebooting</span><br />
From the VPS terminal:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>cat /proc/sys/kernel/random/boot_id</code></div></div>Keep this value locally. After the reboot it must differ, proving you tested a new server boot rather than a simple browser refresh.<br />
<br />
Reboot only after your work is saved and you have a working way to reconnect. In a disposable test VPS, a normal reboot command is:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>sudo reboot</code></div></div>An SSH disconnection is expected while the server restarts. A browser tab can also lose its aaPanel session; signing back into the panel is separate from restarting the application.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">5. Verify recovery without manually starting the app</span><br />
Reconnect through SSH after the VPS is reachable. Check:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>cat /proc/sys/kernel/random/boot_id
ss -lntp | grep ':3200'
curl -i http://127.0.0.1:3200/
curl -i https://your-hostname/</code></div></div>Wait for the native daemon's recovery interval if the application has not returned yet. Do not click Start during the automatic recovery test, because that would invalidate the test.<br />
<br />
Our new boot ID differed from the original. Gunicorn returned automatically with a master and worker listening on <span style="font-weight: bold;" class="mycode_b">127.0.0.1:3200</span>. After restoring the SSL listener described above, the public Flask hostname returned <span style="font-weight: bold;" class="mycode_b">HTTP 200</span> with <span style="font-weight: bold;" class="mycode_b">AdminsForum Flask lab</span> and passed certificate validation. Nginx, Postfix and Dovecot were also active.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">6. Inspect logs if recovery fails</span><br />
In the project window, open <span style="font-weight: bold;" class="mycode_b">Project logs</span>. Check the latest startup attempt rather than old messages. For this project, the error log was:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/www/wwwlogs/python/af_flask_lab/error.log</code></div></div>Typical problems include a missing virtual environment, incorrect working directory, missing packages, a port already in use or a startup user unable to read the app files. Confirm that the same command still starts the app in its intended environment before changing the daemon.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Know the limits of this test</span><ul class="mycode_list"><li>A successful reboot test confirms recovery for the tested app and configuration. It does not test database recovery, queues, upload persistence or every application dependency.<br />
</li>
<li>Startup recovery can cause a short period of 502 responses while Nginx is running but Gunicorn has not started yet.<br />
</li>
<li>Because the native daemon recovers stopped projects, temporarily disable Startup when deliberately keeping an app stopped for maintenance; restore it when the maintenance is finished.<br />
</li>
<li>Repeat HTTP and HTTPS checks after later project edits. The SSL configuration issue above was observed on this lab version, not assumed for every aaPanel release.<br />
</li>
</ul>
<br />
<span style="font-weight: bold;" class="mycode_b">Completion checklist</span><ul class="mycode_list"><li>Startup checkbox enabled and saved.<br />
</li>
<li>Correct environment, project path and non-root user retained.<br />
</li>
<li>A real reboot confirmed by a changed boot ID.<br />
</li>
<li>App returned without a manual Start action.<br />
</li>
<li>Local response, public mapping and HTTPS checked separately.<br />
</li>
<li>Any recovery errors explained using current logs.<br />
</li>
</ul>
<br />
<span style="font-style: italic;" class="mycode_i">Tested on the AdminsForum lab VPS. Screenshot and test results are from our own deployment.</span>]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[Connect a Flask App to a Domain and HTTPS in aaPanel]]></title>
			<link>https://adminsforum.com/Thread-guide-connect-a-flask-app-to-a-domain-and-https-in-aapanel</link>
			<pubDate>Wed, 14 Oct 2026 14:34:55 +0800</pubDate>
			<dc:creator><![CDATA[<a href="https://adminsforum.com/member.php?action=profile&uid=2">DEV</a>]]></dc:creator>
			<guid isPermaLink="false">https://adminsforum.com/Thread-guide-connect-a-flask-app-to-a-domain-and-https-in-aapanel</guid>
			<description><![CDATA[<span style="font-weight: bold;" class="mycode_b">A Flask app can work on localhost while still being unavailable through its domain. This guide connects a running Gunicorn project to Nginx, points a hostname at the VPS and enables HTTPS with a trusted certificate.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Tested lab:</span> Ubuntu 22.04.5, aaPanel Free 8.0.7, Nginx 1.30.5, Flask 3.1.3 and Gunicorn 26.2.0. The public test hostname is flask.adminslab.com. Replace the hostname and VPS address with your own values. Menu wording can vary by panel version.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Before you start</span><ul class="mycode_list"><li>Complete <a href="https://adminsforum.com/Thread-guide-run-a-flask-app-with-gunicorn-in-aapanel" target="_blank" rel="noopener" class="mycode_url">Run a Flask App with Gunicorn in aaPanel</a>. This lesson assumes the Python project is already running.<br />
</li>
<li>Have control of the domain's DNS. Nginx must be installed and the VPS must accept incoming TCP 80 and 443 through both its server firewall and any provider firewall.<br />
</li>
<li>The application should listen on localhost, not a public interface. Our Gunicorn command is:<br />
</li>
</ul>
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>gunicorn --workers 1 --bind 127.0.0.1:3200 app:app</code></div></div>Port 3200 is the private application port. Visitors use 80 or 443 through Nginx; you do not need to open 3200 to the Internet.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">1. Check the application before changing DNS</span><br />
In aaPanel, open <span style="font-weight: bold;" class="mycode_b">Website → Python Project</span>. Check that your project is running. Our project is <span style="font-weight: bold;" class="mycode_b">af_flask_lab</span>. If it does not start, open <span style="font-weight: bold;" class="mycode_b">Project logs</span> and fix the startup error first.<br />
<br />
From the VPS terminal, test:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>curl -i http://127.0.0.1:3200/</code></div></div>Our test returned HTTP 200 and the body <span style="font-weight: bold;" class="mycode_b">AdminsForum Flask lab</span>. If this fails, a certificate or DNS change will not repair the application itself.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">2. Add the domain to the Python project</span><br />
Click the project name to open its management window. Select <span style="font-weight: bold;" class="mycode_b">Domain Manager</span>, enter your full hostname (for example, flask.example.com) without https:// or a path, and add it on port 80. Confirm that the domain appears in the project's domain list.<br />
<br />
Do not create a separate PHP website with the same hostname. The Python project should own the hostname so Nginx sends requests to the correct application.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">3. Point the DNS record at the VPS</span><br />
If the domain uses Cloudflare nameservers, open the domain in Cloudflare and choose <span style="font-weight: bold;" class="mycode_b">DNS → Records → Add record</span>. For our subdomain we used:<ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">Type:</span> A<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Name:</span> flask<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">IPv4 address:</span> the lab VPS public IPv4 address<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Proxy status:</span> DNS only for this direct-origin certificate test<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">TTL:</span> Auto<br />
</li>
</ul>
Save the record. Check for conflicting records for the same hostname. An AAAA record must point to a working IPv6 origin; do not leave an incorrect IPv6 record in place.<br />
<br />
DNS only makes the hostname resolve directly to the VPS. This guide tests Nginx and its certificate at the origin; Cloudflare proxy configuration is a separate step. Allow DNS propagation before requesting the certificate.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">4. Enable external network mapping</span><br />
Return to the project's <span style="font-weight: bold;" class="mycode_b">Mapping</span> section. Turn on <span style="font-weight: bold;" class="mycode_b">External network mapping</span> and confirm the panel's prompt. The panel should detect the running application port. In our lab it detected <span style="font-weight: bold;" class="mycode_b">3200</span> and showed <span style="font-weight: bold;" class="mycode_b">Proxy routing is enabled: /</span>.<br />
<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://adminsforum.com/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=216" target="_blank" title="">flask-mapping-20261014.jpg</a> (Size: 227.67 KB / Downloads: 4)
<!-- end: postbit_attachments_attachment --><br />
<span style="font-style: italic;" class="mycode_i">The Python project's mapping routes website traffic through Nginx to the local application port.</span><br />
<br />
The <span style="font-weight: bold;" class="mycode_b">Firewall Status: Not configured</span> entry for 3200 does not mean you must expose that port. The browser reaches Nginx on 80/443, and Nginx reaches Gunicorn on 127.0.0.1:3200.<br />
<br />
Open <a href="http://your-hostname/" target="_blank" rel="noopener" class="mycode_url">http://your-hostname/</a> and check that it displays your Flask response. Our public HTTP check returned <span style="font-weight: bold;" class="mycode_b">HTTP 200</span>. If you see another website, check domain ownership and DNS. If you see 502, check the running process, its bind address and the mapping port.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">5. Apply a Let's Encrypt certificate</span><br />
In the project's management window, open <span style="font-weight: bold;" class="mycode_b">SSL → Let's Encrypt</span>. Choose <span style="font-weight: bold;" class="mycode_b">File Verification</span>, select the exact hostname and click <span style="font-weight: bold;" class="mycode_b">Apply</span>. File verification requires the certificate authority to reach the hostname on port 80.<br />
<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://adminsforum.com/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=217" target="_blank" title="">flask-ssl-request-20261014.jpg</a> (Size: 161.76 KB / Downloads: 4)
<!-- end: postbit_attachments_attachment --><br />
<span style="font-style: italic;" class="mycode_i">Select only the hostname that resolves to this project before applying for its certificate.</span><br />
<br />
Some panel versions display <span style="font-weight: bold;" class="mycode_b">Not connected to the Domain Management Center</span> and a <span style="font-weight: bold;" class="mycode_b">Connect</span> action. Review that integration before connecting your domain, then return to the certificate request. Wait for issuance to finish; a pending request is not an installed certificate.<br />
<br />
In this lab, the SSL section changed to <span style="font-weight: bold;" class="mycode_b">Current Certs — Deployed</span>, listed <span style="font-weight: bold;" class="mycode_b">flask.adminslab.com</span> and showed an expiry date of <span style="font-weight: bold;" class="mycode_b">12 January 2027</span>. We also validated the live certificate independently. Do not copy or share the private-key field from the certificate screen.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">6. Verify HTTPS from outside the VPS</span><br />
Open <a href="https://your-hostname/" target="_blank" rel="noopener" class="mycode_url">https://your-hostname/</a> in a browser. The page must display the expected application without a certificate warning. From a terminal, you can also test:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>curl -i https://your-hostname/</code></div></div>Do not use curl -k for this check: it skips certificate validation and can hide a broken certificate.<br />
<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://adminsforum.com/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=218" target="_blank" title="">flask-https-result-20261014.jpg</a> (Size: 27.6 KB / Downloads: 4)
<!-- end: postbit_attachments_attachment --><br />
<span style="font-style: italic;" class="mycode_i">The public HTTPS hostname serves the expected Flask lab response.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Our result:</span> <a href="https://flask.adminslab.com/" target="_blank" rel="noopener" class="mycode_url">https://flask.adminslab.com/</a> returned HTTP 200 with <span style="font-weight: bold;" class="mycode_b">AdminsForum Flask lab</span>, and certificate validation passed. This confirms DNS, Nginx mapping, the running application and HTTPS worked together at the time of testing.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Troubleshooting</span><ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">DNS points to the wrong server:</span> correct the A record and any conflicting AAAA record, then allow caches to expire.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">HTTP works but certificate verification fails:</span> check port 80, redirects, proxy rules and whether the selected hostname is the one attached to this project. Read the issuance error rather than repeatedly submitting requests.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">HTTPS shows another site's certificate:</span> confirm this hostname has its own deployed certificate and is served by the expected Nginx project.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">502 Bad Gateway:</span> verify localhost responds, then compare the Gunicorn port with the mapping port and inspect Project logs.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">App stops after reboot:</span> this guide does not verify Python reboot recovery. Test and document autostart separately before treating the deployment as production-ready.<br />
</li>
</ul>
<br />
<span style="font-weight: bold;" class="mycode_b">Completion checklist</span><ul class="mycode_list"><li>Domain is listed under the correct Python project.<br />
</li>
<li>DNS resolves to the intended VPS.<br />
</li>
<li>Local Gunicorn and public HTTP both return the expected content.<br />
</li>
<li>Mapping uses the application's local port.<br />
</li>
<li>SSL is deployed for the exact hostname.<br />
</li>
<li>Public HTTPS passes normal certificate validation and returns the expected app.<br />
</li>
</ul>
<br />
<span style="font-weight: bold;" class="mycode_b">Related:</span> <a href="https://adminsforum.com/Thread-guide-create-a-python-virtual-environment-in-aapanel" target="_blank" rel="noopener" class="mycode_url">Create a Python Virtual Environment</a> · <a href="https://adminsforum.com/Thread-guide-run-a-flask-app-with-gunicorn-in-aapanel" target="_blank" rel="noopener" class="mycode_url">Run Flask with Gunicorn</a><br />
<br />
<span style="font-style: italic;" class="mycode_i">Tested and illustrated by AdminsForum. Screenshots show our own lab; use your own domain and application values.</span>]]></description>
			<content:encoded><![CDATA[<span style="font-weight: bold;" class="mycode_b">A Flask app can work on localhost while still being unavailable through its domain. This guide connects a running Gunicorn project to Nginx, points a hostname at the VPS and enables HTTPS with a trusted certificate.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Tested lab:</span> Ubuntu 22.04.5, aaPanel Free 8.0.7, Nginx 1.30.5, Flask 3.1.3 and Gunicorn 26.2.0. The public test hostname is flask.adminslab.com. Replace the hostname and VPS address with your own values. Menu wording can vary by panel version.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Before you start</span><ul class="mycode_list"><li>Complete <a href="https://adminsforum.com/Thread-guide-run-a-flask-app-with-gunicorn-in-aapanel" target="_blank" rel="noopener" class="mycode_url">Run a Flask App with Gunicorn in aaPanel</a>. This lesson assumes the Python project is already running.<br />
</li>
<li>Have control of the domain's DNS. Nginx must be installed and the VPS must accept incoming TCP 80 and 443 through both its server firewall and any provider firewall.<br />
</li>
<li>The application should listen on localhost, not a public interface. Our Gunicorn command is:<br />
</li>
</ul>
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>gunicorn --workers 1 --bind 127.0.0.1:3200 app:app</code></div></div>Port 3200 is the private application port. Visitors use 80 or 443 through Nginx; you do not need to open 3200 to the Internet.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">1. Check the application before changing DNS</span><br />
In aaPanel, open <span style="font-weight: bold;" class="mycode_b">Website → Python Project</span>. Check that your project is running. Our project is <span style="font-weight: bold;" class="mycode_b">af_flask_lab</span>. If it does not start, open <span style="font-weight: bold;" class="mycode_b">Project logs</span> and fix the startup error first.<br />
<br />
From the VPS terminal, test:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>curl -i http://127.0.0.1:3200/</code></div></div>Our test returned HTTP 200 and the body <span style="font-weight: bold;" class="mycode_b">AdminsForum Flask lab</span>. If this fails, a certificate or DNS change will not repair the application itself.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">2. Add the domain to the Python project</span><br />
Click the project name to open its management window. Select <span style="font-weight: bold;" class="mycode_b">Domain Manager</span>, enter your full hostname (for example, flask.example.com) without https:// or a path, and add it on port 80. Confirm that the domain appears in the project's domain list.<br />
<br />
Do not create a separate PHP website with the same hostname. The Python project should own the hostname so Nginx sends requests to the correct application.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">3. Point the DNS record at the VPS</span><br />
If the domain uses Cloudflare nameservers, open the domain in Cloudflare and choose <span style="font-weight: bold;" class="mycode_b">DNS → Records → Add record</span>. For our subdomain we used:<ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">Type:</span> A<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Name:</span> flask<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">IPv4 address:</span> the lab VPS public IPv4 address<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Proxy status:</span> DNS only for this direct-origin certificate test<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">TTL:</span> Auto<br />
</li>
</ul>
Save the record. Check for conflicting records for the same hostname. An AAAA record must point to a working IPv6 origin; do not leave an incorrect IPv6 record in place.<br />
<br />
DNS only makes the hostname resolve directly to the VPS. This guide tests Nginx and its certificate at the origin; Cloudflare proxy configuration is a separate step. Allow DNS propagation before requesting the certificate.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">4. Enable external network mapping</span><br />
Return to the project's <span style="font-weight: bold;" class="mycode_b">Mapping</span> section. Turn on <span style="font-weight: bold;" class="mycode_b">External network mapping</span> and confirm the panel's prompt. The panel should detect the running application port. In our lab it detected <span style="font-weight: bold;" class="mycode_b">3200</span> and showed <span style="font-weight: bold;" class="mycode_b">Proxy routing is enabled: /</span>.<br />
<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://adminsforum.com/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=216" target="_blank" title="">flask-mapping-20261014.jpg</a> (Size: 227.67 KB / Downloads: 4)
<!-- end: postbit_attachments_attachment --><br />
<span style="font-style: italic;" class="mycode_i">The Python project's mapping routes website traffic through Nginx to the local application port.</span><br />
<br />
The <span style="font-weight: bold;" class="mycode_b">Firewall Status: Not configured</span> entry for 3200 does not mean you must expose that port. The browser reaches Nginx on 80/443, and Nginx reaches Gunicorn on 127.0.0.1:3200.<br />
<br />
Open <a href="http://your-hostname/" target="_blank" rel="noopener" class="mycode_url">http://your-hostname/</a> and check that it displays your Flask response. Our public HTTP check returned <span style="font-weight: bold;" class="mycode_b">HTTP 200</span>. If you see another website, check domain ownership and DNS. If you see 502, check the running process, its bind address and the mapping port.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">5. Apply a Let's Encrypt certificate</span><br />
In the project's management window, open <span style="font-weight: bold;" class="mycode_b">SSL → Let's Encrypt</span>. Choose <span style="font-weight: bold;" class="mycode_b">File Verification</span>, select the exact hostname and click <span style="font-weight: bold;" class="mycode_b">Apply</span>. File verification requires the certificate authority to reach the hostname on port 80.<br />
<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://adminsforum.com/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=217" target="_blank" title="">flask-ssl-request-20261014.jpg</a> (Size: 161.76 KB / Downloads: 4)
<!-- end: postbit_attachments_attachment --><br />
<span style="font-style: italic;" class="mycode_i">Select only the hostname that resolves to this project before applying for its certificate.</span><br />
<br />
Some panel versions display <span style="font-weight: bold;" class="mycode_b">Not connected to the Domain Management Center</span> and a <span style="font-weight: bold;" class="mycode_b">Connect</span> action. Review that integration before connecting your domain, then return to the certificate request. Wait for issuance to finish; a pending request is not an installed certificate.<br />
<br />
In this lab, the SSL section changed to <span style="font-weight: bold;" class="mycode_b">Current Certs — Deployed</span>, listed <span style="font-weight: bold;" class="mycode_b">flask.adminslab.com</span> and showed an expiry date of <span style="font-weight: bold;" class="mycode_b">12 January 2027</span>. We also validated the live certificate independently. Do not copy or share the private-key field from the certificate screen.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">6. Verify HTTPS from outside the VPS</span><br />
Open <a href="https://your-hostname/" target="_blank" rel="noopener" class="mycode_url">https://your-hostname/</a> in a browser. The page must display the expected application without a certificate warning. From a terminal, you can also test:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>curl -i https://your-hostname/</code></div></div>Do not use curl -k for this check: it skips certificate validation and can hide a broken certificate.<br />
<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://adminsforum.com/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=218" target="_blank" title="">flask-https-result-20261014.jpg</a> (Size: 27.6 KB / Downloads: 4)
<!-- end: postbit_attachments_attachment --><br />
<span style="font-style: italic;" class="mycode_i">The public HTTPS hostname serves the expected Flask lab response.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Our result:</span> <a href="https://flask.adminslab.com/" target="_blank" rel="noopener" class="mycode_url">https://flask.adminslab.com/</a> returned HTTP 200 with <span style="font-weight: bold;" class="mycode_b">AdminsForum Flask lab</span>, and certificate validation passed. This confirms DNS, Nginx mapping, the running application and HTTPS worked together at the time of testing.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Troubleshooting</span><ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">DNS points to the wrong server:</span> correct the A record and any conflicting AAAA record, then allow caches to expire.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">HTTP works but certificate verification fails:</span> check port 80, redirects, proxy rules and whether the selected hostname is the one attached to this project. Read the issuance error rather than repeatedly submitting requests.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">HTTPS shows another site's certificate:</span> confirm this hostname has its own deployed certificate and is served by the expected Nginx project.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">502 Bad Gateway:</span> verify localhost responds, then compare the Gunicorn port with the mapping port and inspect Project logs.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">App stops after reboot:</span> this guide does not verify Python reboot recovery. Test and document autostart separately before treating the deployment as production-ready.<br />
</li>
</ul>
<br />
<span style="font-weight: bold;" class="mycode_b">Completion checklist</span><ul class="mycode_list"><li>Domain is listed under the correct Python project.<br />
</li>
<li>DNS resolves to the intended VPS.<br />
</li>
<li>Local Gunicorn and public HTTP both return the expected content.<br />
</li>
<li>Mapping uses the application's local port.<br />
</li>
<li>SSL is deployed for the exact hostname.<br />
</li>
<li>Public HTTPS passes normal certificate validation and returns the expected app.<br />
</li>
</ul>
<br />
<span style="font-weight: bold;" class="mycode_b">Related:</span> <a href="https://adminsforum.com/Thread-guide-create-a-python-virtual-environment-in-aapanel" target="_blank" rel="noopener" class="mycode_url">Create a Python Virtual Environment</a> · <a href="https://adminsforum.com/Thread-guide-run-a-flask-app-with-gunicorn-in-aapanel" target="_blank" rel="noopener" class="mycode_url">Run Flask with Gunicorn</a><br />
<br />
<span style="font-style: italic;" class="mycode_i">Tested and illustrated by AdminsForum. Screenshots show our own lab; use your own domain and application values.</span>]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[Run a Flask App with Gunicorn in aaPanel]]></title>
			<link>https://adminsforum.com/Thread-guide-run-a-flask-app-with-gunicorn-in-aapanel</link>
			<pubDate>Wed, 14 Oct 2026 14:23:45 +0800</pubDate>
			<dc:creator><![CDATA[<a href="https://adminsforum.com/member.php?action=profile&uid=2">DEV</a>]]></dc:creator>
			<guid isPermaLink="false">https://adminsforum.com/Thread-guide-run-a-flask-app-with-gunicorn-in-aapanel</guid>
			<description><![CDATA[<span style="font-weight: bold;" class="mycode_b">This guide creates a small Flask application and runs it under aaPanel using Gunicorn and a separate Python virtual environment. The test stops at a verified localhost response; domain mapping and HTTPS are separate steps.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Tested lab:</span> Ubuntu 22.04.5 LTS, aaPanel Free 8.0.7, Python 3.10.12 in af_python_lab, Flask 3.1.3 and Gunicorn 26.2.0. Tested on 14 October 2026. For a new production project choose a currently maintained Python release compatible with the application; the version here records the existing lab system interpreter.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">1. Prepare the environment</span><br />
Follow <a href="https://adminsforum.com/Thread-guide-create-a-python-virtual-environment-in-aapanel" target="_blank" rel="noopener" class="mycode_url">Create a Python virtual environment in aaPanel</a>. Our application interpreter is:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/www/server/pyporject_evn/af_python_lab/bin/python</code></div></div>Keep application dependencies separate from aaPanel's own pyenv. Verify the interpreter and pip before installing packages.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">2. Create the application folder</span><br />
Create a fresh folder /www/wwwroot/af-flask-lab and put app.py inside it:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>from flask import Flask

app = Flask(__name__)

@app.get('/')
def home():
    return 'AdminsForum Flask lab'</code></div></div>This defines a Flask application object named app in a module named app. There are no credentials, database operations or file uploads in this demonstration.<br />
<br />
Use www ownership for this new app folder so the selected run user can read it. The app environment also needs suitable ownership for package installation by the intended user. Apply permission changes only to the newly created application environment and folder, not aaPanel's own Python environment.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">3. Install Flask and Gunicorn</span><br />
As www, use the application's exact interpreter:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/www/server/pyporject_evn/af_python_lab/bin/python -m pip install --index-url https://pypi.org/simple flask gunicorn
/www/server/pyporject_evn/af_python_lab/bin/python -m pip check</code></div></div>We installed from official PyPI and the dependency check returned No broken requirements found. For repeatable real deployments, preserve a reviewed dependency specification instead of always installing whatever version is newest.<br />
<br />
Gunicorn is the WSGI server for this example. Do not publish a production Flask site using its development server or enable debug mode on a public deployment.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">4. Add the Python project</span><br />
Open <span style="font-weight: bold;" class="mycode_b">Website → Python Project → Add Project</span>. Set:<ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">Project Name:</span> af_flask_lab<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Python Environment:</span> af_python_lab<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Startup Way:</span> Command Startup<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Project Path:</span> /www/wwwroot/af-flask-lab<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Startup Command:</span> gunicorn --workers 1 --bind 127.0.0.1:3200 app:app<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Startup User:</span> www<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Environment Variables:</span> None for this non-secret sample<br />
</li>
</ul>
The folder field can update the project name automatically in this version; check the final identifier before confirming. We left the dependency-path field empty because the packages were already installed in the selected environment.<br />
<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://adminsforum.com/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=213" target="_blank" title="">flask-project-create-20261014.jpg</a> (Size: 164.44 KB / Downloads: 4)
<!-- end: postbit_attachments_attachment --><br />
<span style="font-style: italic;" class="mycode_i">Create the Flask project with the isolated interpreter and localhost Gunicorn command.</span><br />
<br />
The app:app argument means load the app object from app.py. The port must be available. One worker is enough for this tiny lab check, not a universal production sizing recommendation.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">5. Wait for project initialization</span><br />
Click Confirm and let initialization finish. Our panel performed additional environment setup before launching the process. A creation dialog completing does not guarantee that the app is already ready for requests.<br />
<br />
Click the project name and inspect Project information. Confirm the interpreter, source folder, run user and startup command.<br />
<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://adminsforum.com/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=214" target="_blank" title="">flask-project-final-20261014.jpg</a> (Size: 144.9 KB / Downloads: 4)
<!-- end: postbit_attachments_attachment --><br />
<span style="font-style: italic;" class="mycode_i">Final project settings retain af_python_lab, the www user and port 3200.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">6. Read the startup log</span><br />
Open <span style="font-weight: bold;" class="mycode_b">Project logs</span>. Our log showed Gunicorn starting, listening at <a href="http://127.0.0.1:3200" target="_blank" rel="noopener" class="mycode_url">http://127.0.0.1:3200</a> and booting its worker.<br />
<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://adminsforum.com/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=215" target="_blank" title="">flask-project-logs-20261014.jpg</a> (Size: 155.79 KB / Downloads: 4)
<!-- end: postbit_attachments_attachment --><br />
<span style="font-style: italic;" class="mycode_i">Actual Gunicorn startup output for the sample project.</span><br />
<br />
If the log reports module not found, check the selected environment and working directory. If it reports address already in use, identify the existing listener before trying another launch. Read the complete error before reinstalling packages.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">7. Verify the local response</span><br />
Over SSH on the VPS:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>curl -i http://127.0.0.1:3200/
ss -ltnp | grep ':3200'
ps -eo user,pid,args | grep '[g]unicorn.*3200'</code></div></div>Our response was HTTP 200 with AdminsForum Flask lab. The Gunicorn master and worker ran as www using the selected environment. pip check passed after the panel setup, and the existing Node.js HTTPS website remained healthy.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">8. Understand the boundary of this test</span><br />
The app is listening only on localhost. It is not yet a public HTTPS website. Next, add the intended domain, configure web-server mapping to this port and deploy a certificate. Do not open port 3200 publicly just to bypass the mapping.<br />
<br />
Startup-after-reboot is also a separate recovery test. The Startup checkbox for this Python sample remained off during the current test. A successful current response does not prove automatic reboot recovery.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Reference:</span> <a href="https://flask.palletsprojects.com/en/stable/deploying/gunicorn/" target="_blank" rel="noopener" class="mycode_url">Flask's Gunicorn deployment documentation</a>.]]></description>
			<content:encoded><![CDATA[<span style="font-weight: bold;" class="mycode_b">This guide creates a small Flask application and runs it under aaPanel using Gunicorn and a separate Python virtual environment. The test stops at a verified localhost response; domain mapping and HTTPS are separate steps.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Tested lab:</span> Ubuntu 22.04.5 LTS, aaPanel Free 8.0.7, Python 3.10.12 in af_python_lab, Flask 3.1.3 and Gunicorn 26.2.0. Tested on 14 October 2026. For a new production project choose a currently maintained Python release compatible with the application; the version here records the existing lab system interpreter.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">1. Prepare the environment</span><br />
Follow <a href="https://adminsforum.com/Thread-guide-create-a-python-virtual-environment-in-aapanel" target="_blank" rel="noopener" class="mycode_url">Create a Python virtual environment in aaPanel</a>. Our application interpreter is:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/www/server/pyporject_evn/af_python_lab/bin/python</code></div></div>Keep application dependencies separate from aaPanel's own pyenv. Verify the interpreter and pip before installing packages.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">2. Create the application folder</span><br />
Create a fresh folder /www/wwwroot/af-flask-lab and put app.py inside it:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>from flask import Flask

app = Flask(__name__)

@app.get('/')
def home():
    return 'AdminsForum Flask lab'</code></div></div>This defines a Flask application object named app in a module named app. There are no credentials, database operations or file uploads in this demonstration.<br />
<br />
Use www ownership for this new app folder so the selected run user can read it. The app environment also needs suitable ownership for package installation by the intended user. Apply permission changes only to the newly created application environment and folder, not aaPanel's own Python environment.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">3. Install Flask and Gunicorn</span><br />
As www, use the application's exact interpreter:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>/www/server/pyporject_evn/af_python_lab/bin/python -m pip install --index-url https://pypi.org/simple flask gunicorn
/www/server/pyporject_evn/af_python_lab/bin/python -m pip check</code></div></div>We installed from official PyPI and the dependency check returned No broken requirements found. For repeatable real deployments, preserve a reviewed dependency specification instead of always installing whatever version is newest.<br />
<br />
Gunicorn is the WSGI server for this example. Do not publish a production Flask site using its development server or enable debug mode on a public deployment.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">4. Add the Python project</span><br />
Open <span style="font-weight: bold;" class="mycode_b">Website → Python Project → Add Project</span>. Set:<ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">Project Name:</span> af_flask_lab<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Python Environment:</span> af_python_lab<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Startup Way:</span> Command Startup<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Project Path:</span> /www/wwwroot/af-flask-lab<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Startup Command:</span> gunicorn --workers 1 --bind 127.0.0.1:3200 app:app<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Startup User:</span> www<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Environment Variables:</span> None for this non-secret sample<br />
</li>
</ul>
The folder field can update the project name automatically in this version; check the final identifier before confirming. We left the dependency-path field empty because the packages were already installed in the selected environment.<br />
<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://adminsforum.com/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=213" target="_blank" title="">flask-project-create-20261014.jpg</a> (Size: 164.44 KB / Downloads: 4)
<!-- end: postbit_attachments_attachment --><br />
<span style="font-style: italic;" class="mycode_i">Create the Flask project with the isolated interpreter and localhost Gunicorn command.</span><br />
<br />
The app:app argument means load the app object from app.py. The port must be available. One worker is enough for this tiny lab check, not a universal production sizing recommendation.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">5. Wait for project initialization</span><br />
Click Confirm and let initialization finish. Our panel performed additional environment setup before launching the process. A creation dialog completing does not guarantee that the app is already ready for requests.<br />
<br />
Click the project name and inspect Project information. Confirm the interpreter, source folder, run user and startup command.<br />
<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://adminsforum.com/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=214" target="_blank" title="">flask-project-final-20261014.jpg</a> (Size: 144.9 KB / Downloads: 4)
<!-- end: postbit_attachments_attachment --><br />
<span style="font-style: italic;" class="mycode_i">Final project settings retain af_python_lab, the www user and port 3200.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">6. Read the startup log</span><br />
Open <span style="font-weight: bold;" class="mycode_b">Project logs</span>. Our log showed Gunicorn starting, listening at <a href="http://127.0.0.1:3200" target="_blank" rel="noopener" class="mycode_url">http://127.0.0.1:3200</a> and booting its worker.<br />
<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://adminsforum.com/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=215" target="_blank" title="">flask-project-logs-20261014.jpg</a> (Size: 155.79 KB / Downloads: 4)
<!-- end: postbit_attachments_attachment --><br />
<span style="font-style: italic;" class="mycode_i">Actual Gunicorn startup output for the sample project.</span><br />
<br />
If the log reports module not found, check the selected environment and working directory. If it reports address already in use, identify the existing listener before trying another launch. Read the complete error before reinstalling packages.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">7. Verify the local response</span><br />
Over SSH on the VPS:<br />
<div class="codeblock"><div class="title af-code-title"><span>Code:</span><button type="button" class="af-code-copy" aria-label="Copy code" title="Copy code"><i class="far fa-copy" aria-hidden="true"></i><span>Copy</span></button><span class="af-code-status" role="status" aria-live="polite"></span></div><div class="body" dir="ltr"><code>curl -i http://127.0.0.1:3200/
ss -ltnp | grep ':3200'
ps -eo user,pid,args | grep '[g]unicorn.*3200'</code></div></div>Our response was HTTP 200 with AdminsForum Flask lab. The Gunicorn master and worker ran as www using the selected environment. pip check passed after the panel setup, and the existing Node.js HTTPS website remained healthy.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">8. Understand the boundary of this test</span><br />
The app is listening only on localhost. It is not yet a public HTTPS website. Next, add the intended domain, configure web-server mapping to this port and deploy a certificate. Do not open port 3200 publicly just to bypass the mapping.<br />
<br />
Startup-after-reboot is also a separate recovery test. The Startup checkbox for this Python sample remained off during the current test. A successful current response does not prove automatic reboot recovery.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Reference:</span> <a href="https://flask.palletsprojects.com/en/stable/deploying/gunicorn/" target="_blank" rel="noopener" class="mycode_url">Flask's Gunicorn deployment documentation</a>.]]></content:encoded>
		</item>
	</channel>
</rss>