<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: John Rotherham</title>
    <description>The latest articles on DEV Community by John Rotherham (@coretexseo).</description>
    <link>https://dev.to/coretexseo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4198518%2Faf12db28-7270-4e07-a8a3-e678806d6902.png</url>
      <title>DEV Community: John Rotherham</title>
      <link>https://dev.to/coretexseo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/coretexseo"/>
    <language>en</language>
    <item>
      <title>Connecting a Self-Hosted Ollama Server to WordPress (and Why localhost Won't Work)</title>
      <dc:creator>John Rotherham</dc:creator>
      <pubDate>Wed, 21 Oct 2026 14:13:07 +0000</pubDate>
      <link>https://dev.to/coretexseo/connecting-a-self-hosted-ollama-server-to-wordpress-and-why-localhost-wont-work-mi9</link>
      <guid>https://dev.to/coretexseo/connecting-a-self-hosted-ollama-server-to-wordpress-and-why-localhost-wont-work-mi9</guid>
      <description>&lt;p&gt;More WordPress plugins now let you "bring your own AI": paste in an API key and the plugin sends prompts to OpenAI, Anthropic or whoever you choose. Many also support Ollama, which runs open models on hardware you control. No API key, no per-request bill, and your content doesn't leave your infrastructure.&lt;/p&gt;

&lt;p&gt;It sounds simple: install Ollama, point the plugin at &lt;code&gt;http://localhost:11434&lt;/code&gt;, done. Then the connection test fails, and it's not obvious why.&lt;/p&gt;

&lt;p&gt;This post walks through connecting a self-hosted Ollama server to WordPress properly: where Ollama needs to run, how to expose it safely, and how to test the connection from the WordPress side before blaming the plugin.&lt;/p&gt;

&lt;h2&gt;
  
  
  The localhost trap
&lt;/h2&gt;

&lt;p&gt;Here's the bit that catches people out. When a WordPress plugin calls &lt;code&gt;http://localhost:11434&lt;/code&gt;, the request is made &lt;strong&gt;by PHP on the web server&lt;/strong&gt;. "localhost" means the web server itself, not your laptop.&lt;/p&gt;

&lt;p&gt;So:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ollama on your laptop + WordPress on a host:&lt;/strong&gt; won't work. The host can't see your laptop's localhost.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shared hosting:&lt;/strong&gt; won't work. You can't install and run a long-lived Ollama process there, and the server won't have the memory for a model anyway.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ollama on the same VPS or dedicated server as WordPress:&lt;/strong&gt; works with the default &lt;code&gt;localhost&lt;/code&gt; URL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ollama on a separate machine (say, a GPU box) that the WordPress server can reach:&lt;/strong&gt; works, with the server's address instead of &lt;code&gt;localhost&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you develop locally with WordPress and Ollama on the same laptop, localhost works fine. It breaks the moment you deploy, which is why it's worth understanding now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Install Ollama and pull a model
&lt;/h2&gt;

&lt;p&gt;On a Linux server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://ollama.com/install.sh | sh
ollama pull llama3.2
ollama list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model has to be pulled on the Ollama server before any plugin can use it. Plugins generally can't pull models for you. &lt;code&gt;llama3.2&lt;/code&gt; is just an example here; pick whatever fits your hardware, and test the output quality on your own content before relying on it. Smaller models run faster on modest hardware; larger ones need much more RAM or a GPU.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Decide what Ollama listens on
&lt;/h2&gt;

&lt;p&gt;By default, Ollama listens on &lt;code&gt;127.0.0.1:11434&lt;/code&gt;, so only the same machine can reach it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Same server as WordPress?&lt;/strong&gt; Leave it alone. Use &lt;code&gt;http://localhost:11434&lt;/code&gt; in the plugin.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Separate server?&lt;/strong&gt; Tell Ollama to listen on an address the WordPress server can reach. With the Linux install it runs as a systemd service, so add an override:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl edit ollama.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[Service]&lt;/span&gt;
&lt;span class="py"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"OLLAMA_HOST=10.0.0.5:11434"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl daemon-reload
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart ollama
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bind to a &lt;strong&gt;private network address&lt;/strong&gt; (like &lt;code&gt;10.0.0.5&lt;/code&gt; above) if your two servers share one. &lt;code&gt;0.0.0.0&lt;/code&gt; also works, but it listens on every interface, including the public one, so it leans entirely on your firewall.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Lock it down
&lt;/h2&gt;

&lt;p&gt;Ollama's API has no built-in authentication. Anyone who can reach port 11434 can run prompts on your hardware. And many WordPress integrations send a plain request with &lt;strong&gt;no &lt;code&gt;Authorization&lt;/code&gt; header&lt;/strong&gt;, so putting an auth-checking reverse proxy in front won't help unless the plugin supports it.&lt;/p&gt;

&lt;p&gt;That means the protection has to be at the network level:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Allow only the WordPress server to reach Ollama&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw allow from 203.0.113.10 to any port 11434 proto tcp
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw deny 11434/tcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Better still, keep the traffic on a private network between the two servers (your provider's VPC, or a WireGuard/Tailscale link) so port 11434 never faces the internet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Test from the WordPress server's shell
&lt;/h2&gt;

&lt;p&gt;Before touching WordPress, SSH into the &lt;strong&gt;WordPress server&lt;/strong&gt; (not your laptop) and check that it can reach Ollama:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Can we connect, and which models are installed?&lt;/span&gt;
curl http://10.0.0.5:11434/api/tags

&lt;span class="c"&gt;# Does generation work?&lt;/span&gt;
curl http://10.0.0.5:11434/api/generate &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"model": "llama3.2", "prompt": "Write a page title for a bakery in Leeds.", "stream": false}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;"stream": false&lt;/code&gt; returns one JSON object with the full answer in its &lt;code&gt;response&lt;/code&gt; field, instead of a stream of chunks. That's the format most PHP integrations expect.&lt;/p&gt;

&lt;p&gt;If the first command hangs or says "connection refused", it's the bind address or the firewall. If the second complains that the model isn't found, you haven't pulled it on that server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Test from PHP inside WordPress
&lt;/h2&gt;

&lt;p&gt;The shell test proves the network path. This one proves PHP and the WordPress HTTP API can use it too. Save as &lt;code&gt;ollama-test.php&lt;/code&gt; and run it with WP-CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// Run with: wp eval-file ollama-test.php&lt;/span&gt;
&lt;span class="nv"&gt;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'http://10.0.0.5:11434/api/generate'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;wp_remote_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'timeout'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'headers'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'application/json'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;'body'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;wp_json_encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;'model'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'llama3.2'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'prompt'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Write a meta description for a bakery in Leeds.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'stream'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nf"&gt;is_wp_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="no"&gt;WP_CLI&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get_error_message&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nv"&gt;$code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;wp_remote_retrieve_response_code&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;json_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nf"&gt;wp_remote_retrieve_body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="no"&gt;WP_CLI&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$code&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;': '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'response'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'error'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s1"&gt;'Empty body'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wp eval-file ollama-test.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;cURL error 7&lt;/code&gt; here means PHP can't connect, which is the same network problem as Step 4. A &lt;code&gt;cURL error 28&lt;/code&gt; is a timeout, which brings us to performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Mind the timeouts
&lt;/h2&gt;

&lt;p&gt;Plugins set their own HTTP timeouts, and a local model on modest hardware can be slow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The first request is the slowest&lt;/strong&gt;, because Ollama loads the model into memory. By default it unloads an idle model after a few minutes; the &lt;code&gt;OLLAMA_KEEP_ALIVE&lt;/code&gt; environment variable changes that.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CPU-only inference on a large model&lt;/strong&gt; may simply take longer than the plugin is willing to wait. If you see timeouts, try a smaller model before anything else.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Batch jobs add up.&lt;/strong&gt; If a plugin processes several pages per run, each one is a separate request. Start with small batches.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 7: Point the plugin at it
&lt;/h2&gt;

&lt;p&gt;Once the WP-CLI test passes, the plugin side is usually just a URL and a model name. As one example, in Coretex SEO (the plugin I work on) it's &lt;strong&gt;Coretex SEO → ⚙ Settings → AI Engine Settings&lt;/strong&gt;: choose an Ollama model in &lt;strong&gt;Brain (Provider + Model)&lt;/strong&gt;, enter the &lt;strong&gt;Ollama Server URL&lt;/strong&gt;, then click &lt;strong&gt;Save Settings&lt;/strong&gt; and &lt;strong&gt;Test Connection&lt;/strong&gt;. There's no key field for Ollama. Its scans then put each suggested title or meta description into a review queue with &lt;strong&gt;Edit | Apply | Dismiss&lt;/strong&gt;, so a person still decides what reaches the page. One limit worth knowing: it talks to your own Ollama server only, so Ollama Cloud isn't supported.&lt;/p&gt;

&lt;p&gt;Other plugins differ in labels, but the checklist is the same: the URL must be reachable from the web server, and the model must already be pulled there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting cheat sheet
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Likely cause&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Works locally, fails after deploy&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;localhost&lt;/code&gt; now means the web server&lt;/td&gt;
&lt;td&gt;Run Ollama on that server, or use a reachable address&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Connection refused / cURL error 7&lt;/td&gt;
&lt;td&gt;Ollama bound to 127.0.0.1, or firewall&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;OLLAMA_HOST&lt;/code&gt;, open the port to the WP server only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Timeout / cURL error 28&lt;/td&gt;
&lt;td&gt;Model too big for the hardware, or cold start&lt;/td&gt;
&lt;td&gt;Smaller model, warm it up, &lt;code&gt;OLLAMA_KEEP_ALIVE&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"model not found"&lt;/td&gt;
&lt;td&gt;Model not pulled on that server&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ollama pull &amp;lt;model&amp;gt;&lt;/code&gt; on the Ollama server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Works on shared hosting?&lt;/td&gt;
&lt;td&gt;It won't&lt;/td&gt;
&lt;td&gt;Use a VPS or dedicated server&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Self-hosting the model behind your WordPress AI features is very doable, as long as you remember that the request comes from the web server. Put Ollama where the web server can reach it, keep port 11434 off the public internet, and test from the WordPress server's own shell and PHP before you blame the plugin.&lt;/p&gt;

&lt;p&gt;And whatever the model writes, keep a human between its output and your live pages. Local models are useful drafters, not editors.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was written with the help of AI and checked by the author.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>ollama</category>
      <category>selfhosted</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
