├── .github
└── workflows
│ └── build.yml
├── CHANGELOG.md
├── README.md
└── install.sh
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 | on: [push, pull_request]
3 |
4 | jobs:
5 | release-osx:
6 | name: macOS Homebrew
7 | runs-on: macos-latest
8 | steps:
9 | - uses: actions/checkout@v2
10 | - name: Run installer
11 | run: |
12 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/digitoimistodude/macos-lemp-setup/master/install.sh)"
13 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ### 1.0.9: 2024-06-26
2 |
3 | * Make sure folders exist
4 | * More verbose output for the install script
5 |
6 | ### 1.0.8: 2024-04-12
7 |
8 | * Add unit tests (GitHub actions) for macOS, Homebrew and install.sh
9 | * Add build status badge
10 | * Rewrite install.sh for PHP 8.3, use variables instead of hardcoded paths and versions
11 | * PHP 8.3 support
12 |
13 | ### 1.0.7: 2023-05-12
14 |
15 | * Fix duplicate symlink for nginx folder
16 | * Add php-fpm user, group and listen to PHP post installation instructions
17 |
18 | ### 1.0.6: 2022-11-28
19 |
20 | * Remove derepcated homebrew/dupes and homebrew/versions taps (part of homebrew/core)
21 | * Remove deprecated LaunchAgents
22 | * Change deprecated homebrew/homebrew-php to shivammathur/php
23 | * Fix install commands for php@7.4
24 |
25 | ### 1.0.5: 2022-08-16
26 |
27 | * Change xdebug.mode from debug to develop so var_dump will be shown correctly
28 | * Fixes and clean ups for macOS M1
29 |
30 | ### 1.0.4: 2021-11-13
31 |
32 | * Fix installer oneliner syntax, change to curl for better reliability
33 |
34 | ### 1.0.3: 2021-09-23
35 |
36 | * Add MailHog for local email testing
37 |
38 | ### 1.0.2: 2021-03-24
39 |
40 | * Start public versioning
41 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # LEMP: Local Environment Made Powerful
2 | [](https://github.com/digitoimistodude/macos-lemp-setup/actions/workflows/build.yml) [](https://github.com/digitoimistodude/macos-lemp-setup/releases)  
3 |
4 | Just kidding, it's really **Linux + nginx [engine x] + MySQL + PHP-FPM**, that's where the LEMP comes from. MacOS LEMP Setup is just like Local by Flywheel or XAMPP, WAMP, Hammer, Anvil etc. tools like this, but it concentrates on the native way of running a web server just by using native Linux packages ported for macOS via Homebrew. It's always fast, always on.
5 |
6 | ## LEMP on other systems
7 |
8 | - [Windows version](https://github.com/digitoimistodude/windows-lemp-setup)
9 | - [Linux version](https://github.com/raikasdev/pop-lemp-setup)
10 |
11 | ## Still the best way to locally develop WordPress.
12 |
13 | 
14 |
15 | **macOS LEMP Setup is Mac only!**
16 |
17 | ### Other platforms
18 |
19 | - Windows: [Setting up a local server on Windows 10 for WordPress theme development (or any web development for that matter)](https://rolle.design/local-server-on-windows-10-for-wordpress-theme-development).
20 | - Linux: [Pop!_OS LEMP setup instructions](https://github.com/raikasdev/pop-lemp-setup) by [raikasdev](https://github.com/raikasdev).
21 |
22 | ## Minimum requirements
23 |
24 | * Latest [Homebrew](https://brew.sh/)
25 | * MacBook Pro M1 (if you like to install this on Intel mac, refer to [this tutorial](https://kevdees.com/macos-12-monterey-nginx-setup-multiple-php-versions/))
26 | * macOS Monterey 12.3 or later
27 |
28 | ## Install local LEMP for macOS
29 |
30 | For *Front End development*, a full Vagrant box, docker container per site or Local by Flywheel is not really needed. If you have a Macbook Pro, you can install local LEMP (Linux, nginx, MariaDB and PHP) with this single liner below.
31 |
32 | Please see [installation steps](#installation) instructions first.
33 |
34 | ```` bash
35 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/digitoimistodude/macos-lemp-setup/master/install.sh)"
36 | ````
37 |
38 | Oneliner may not go through in macOS Big Sur and macOS Monterey, in that caes you need to copy and paste commands manually from [install.sh](https://raw.githubusercontent.com/digitoimistodude/macos-lemp-setup/master/install.sh).
39 |
40 | **Please note:** Don't trust blindly to the script, use only if you know what you are doing. You can view the file [here](https://github.com/digitoimistodude/osx-lemp-setup/blob/master/install.sh) if having doubts what commands are being run. However, script is tested working many times and should be safe to run even if you have some or all of the components already installed.
41 |
42 | ## Table of contents
43 |
44 | 1. [Background](#background)
45 | 2. [Features](#features)
46 | 3. [Requirements](#requirements)
47 | 4. [Installation](#installation)
48 | 5. [Post installations](#post-installations)
49 | 1. [Mailhog](#MailHog)
50 | 6. [Use Linux-style aliases](#use-linux-style-aliases)
51 | 7. [File sizes](#file-sizes)
52 | 8. [XDebug](#xdebug)
53 | 9. [Redis](#redis)
54 | 10. [Troubleshooting](#troubleshooting)
55 |
56 | ### Background
57 |
58 | Read the full story by [@ronilaukkarinen](https://github.com/ronilaukkarinen): **[Moving from Vagrant to a LEMP stack directly on a Macbook Pro (for WordPress development)](https://medium.com/@rolle/moving-from-vagrant-to-a-lemp-stack-directly-on-a-macbook-pro-e935b1bc5a38)**
59 |
60 | ### Features
61 |
62 | - PHP 8.3
63 | - nginx 1.25.3
64 | - Super lightweight
65 | - Native packages
66 | - Always on system service
67 | - HTTPS support
68 | - Consistent with production setup
69 |
70 | ### Requirements
71 |
72 | - [Homebrew](https://brew.sh/)
73 | - macOS, preferably 14.2.1 (Sonoma)
74 | - wget
75 | - [mkcert](https://github.com/FiloSottile/mkcert)
76 |
77 | ### Installation
78 |
79 | 1. Run oneliner installation script `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/digitoimistodude/macos-lemp-setup/master/install.sh)"`
80 | 2. Link PHP executable like this: **Run:** `sudo find / -name 'php'`. When you spot link that looks like this (yours might be different version) */usr/local/Cellar/php@8.3/8.3.3/bin/php*, symlink it to correct location to override MacOS's own file: `sudo ln -s /usr/local/Cellar/php@8.3/8.3.3/bin/php /usr/local/bin/php`
81 | 3. Use PHP path from correct location by adding to your ~/.bash_profile file, `sudo nano ~/.bash_profile` (change your PHP version accordingly)
82 | ``` shell
83 | export PATH="$(brew --prefix php@8.3)/bin:$PATH"
84 | ```
85 | 4. Check the version with `php --version`, it should match the linked file.
86 | 5. Brew should have already handled other links, you can test the correct versions with `sudo mysql --version` (if it's something like _mysql Ver 15.1 Distrib 10.5.5-MariaDB, for osx10.15 (x86_64) using readline 5.1_ it's the correct one) and `sudo nginx -v` (if it's something like nginx version: nginx/1.19.3 it's the correct one)
87 | 6. Add `export PATH="$(brew --prefix php@8.3)/bin:$PATH"` to .bash_profile (or to your zsh profile or to whatever term profile you are currently using)
88 | 7. Go through [post installations](#post-installations)
89 | 8. Enjoy! If you use [dudestack](https://github.com/digitoimistodude/dudestack), please check instructions from [its own repo](https://github.com/digitoimistodude/dudestack).
90 |
91 | ### Post installations
92 |
93 | #### PHP config
94 |
95 | You may want to add your user and group correctly to `/opt/homebrew/etc/php/8.3/php-fpm.d/www.conf` (or wherever your www.conf is, find with `sudo find / -name 'www.conf'`) and set these to the bottom:
96 |
97 | ````
98 | catch_workers_output = yes
99 | php_flag[display_errors] = On
100 | php_admin_value[error_log] = /var/log/fpm-php.www.log
101 | slowlog = /var/log/fpm-php.slow.log
102 | php_admin_flag[log_errors] = On
103 | php_admin_value[memory_limit] = 1024M
104 | request_slowlog_timeout = 10
105 | php_admin_value[upload_max_filesize] = 100M
106 | php_admin_value[post_max_size] = 100M
107 | ````
108 |
109 | Please note, if the file is not found (as the location may also be something like `/System/Volumes/Data/opt/homebrew/etc/php/8.3/php-fpm.d/www.conf`), you can find the correct location with:
110 |
111 | ```
112 | sudo find / -name 'www.conf'
113 | ```
114 |
115 | #### Make sure the PHP runs on correct permissions
116 |
117 | Make sure you have your user and group defined, use these as base (only change `rolle` to your own Mac username):
118 |
119 | ````config
120 | ; Unix user/group of processes
121 | ; Note: The user is mandatory. If the group is not set, the default user's group
122 | ; will be used.
123 | user = rolle
124 | group = admin
125 | ````
126 |
127 | Also make sure you have listen set up properly
128 |
129 | ````config
130 | ; The address on which to accept FastCGI requests.
131 | ; Valid syntaxes are:
132 | ; 'ip.add.re.ss:port' - to listen on a TCP socket to a specific IPv4 address on
133 | ; a specific port;
134 | ; '[ip:6:addr:ess]:port' - to listen on a TCP socket to a specific IPv6 address on
135 | ; a specific port;
136 | ; 'port' - to listen on a TCP socket to all addresses
137 | ; (IPv6 and IPv4-mapped) on a specific port;
138 | ; '/path/to/unix/socket' - to listen on a unix socket.
139 | ; Note: This value is mandatory.
140 | listen = 127.0.0.1:9000
141 | ````
142 |
143 | #### Default nginx config
144 |
145 | Make sure you have default vhost for your site (`/etc/nginx/sites-enabled/sitename.test`) could be something like:
146 |
147 | ```` nginx
148 | server {
149 | listen 80;
150 | root /var/www/example;
151 | index index.html index.htm index.php;
152 | server_name example.test www.example.test;
153 | include php.conf;
154 | include global/wordpress.conf;
155 | }
156 | ````
157 |
158 | #### Default MySQL my.cnf
159 |
160 | Default my.cnf would be something like this (already added by install.sh in `/usr/local/etc/my.cnf`:
161 |
162 | ````
163 | #
164 | # This group is read both both by the client and the server
165 | # use it for options that affect everything
166 | #
167 | [client-server]
168 |
169 | #
170 | # include all files from the config directory
171 | #
172 | !includedir /usr/local/etc/my.cnf.d
173 |
174 | [mysqld]
175 | innodb_log_file_size = 32M
176 | innodb_buffer_pool_size = 1024M
177 | innodb_log_buffer_size = 4M
178 | slow_query_log = 1
179 | query_cache_limit = 512K
180 | query_cache_size = 128M
181 | skip-name-resolve
182 | ````
183 |
184 | Again, if the correct file cannot be found, you can find it with:
185 |
186 | ```
187 | sudo find / -name 'my.cnf'
188 | ```
189 |
190 | For mysql, remember to run `sudo mysql_secure_installation`, answer as suggested, add/change root password, remove test users etc. Only exception! Answer with n to the question Disallow root login remotely? [Y/n]
. Your logs can be found at `/usr/local/var/mysql/yourcomputername.err` (where yourcomputername is obviously your hostname).
191 |
192 | After that, get to know [dudestack](https://github.com/digitoimistodude/dudestack) to get everything up and running smoothly. Current version of dudestack supports macOS LEMP stack.
193 |
194 | You should remember to add vhosts to your /etc/hosts file, for example: `127.0.0.1 site.test`.
195 |
196 | ### Symlinks
197 |
198 | You should find the correct files and link them like in Linux. This helps you to remember the correct paths.
199 |
200 | For example (please note, this is just an example):
201 |
202 | ```bash
203 | sudo mkdir -p /usr/local/bin
204 | sudo ln -s /opt/homebrew/Cellar/php@8.3/8.3.3/bin/php /usr/local/bin/php
205 | sudo ln -s /opt/homebrew/Cellar/php@8.3/8.3.3/sbin/php-fpm /usr/local/bin/php-fpm
206 | sudo ln -s /opt/homebrew/Cellar/php@8.3/8.3.3/sbin/php-fpm /usr/local/bin/php-fpm
207 | sudo ln -s /opt/homebrew/etc/php /etc/php
208 | sudo ln -s /opt/homebrew/etc/nginx /etc/nginx
209 | sudo ln -s /opt/homebrew/etc/my.cnf /etc/my.cnf
210 | ```
211 |
212 | ### Use Linux-style aliases
213 |
214 | Add this to */usr/local/bin/service* and chmod it +x:
215 |
216 | ```` bash
217 | #!/bin/bash
218 | # Alias for unix type of commands
219 | brew services "$2" "$1";
220 | ````
221 |
222 | Now you are able to restart nginx and mysql unix style like this:
223 |
224 | ```` bash
225 | sudo service nginx restart
226 | sudo service mariadb restart
227 | ````
228 |
229 | #### MailHog
230 |
231 | E-mails won't be sent on local environment because there is no email server configured. This is where [MailHog](https://github.com/mailhog/MailHog) comes in.
232 |
233 | MailHog should be pre-installed but if not, run following:
234 |
235 | ``` bash
236 | brew update && brew install mailhog
237 | ```
238 |
239 | Ensure you have the latest [air-helper](https://github.com/digitoimistodude/air-helper) or [MailHog for WordPress](https://wordpress.org/plugins/wp-mailhog-smtp/) activated to enable MailHog routing for local environment.
240 |
241 | Then just run:
242 |
243 | ``` bash
244 | mailhog
245 | ```
246 |
247 | You should now get a log in command line and web interface is available in http://0.0.0.0:8025/.
248 |
249 | ### File sizes
250 |
251 | You might want to increase file sizes for development environment in case you need to test compression plugins and other stuff in WordPress. To do so, edit `/usr/local/etc/php/8.3/php-fpm.d/www.conf` and `/usr/local/etc/php/8.3/php.ini` and change all **memory_limit**, **post_max_size** and **upload_max_filesize** to something that is not so limited, for example **500M**.
252 |
253 | Please note, you also need to change **client_max_body_size** to the same amount in `/etc/nginx/nginx.conf`. After this, restart php-fpm with `sudo brew services restart php@8.3` and nginx with `sudo brew services restart nginx`.
254 |
255 | ### Certificates for localhost
256 |
257 | First things first, if you haven't done it yet, generate general dhparam:
258 |
259 | ```` bash
260 | sudo su -
261 | cd /etc/ssl/certs
262 | sudo openssl dhparam -dsaparam -out dhparam.pem 4096
263 | ````
264 |
265 | Generating certificates for dev environment is easiest with [mkcert](https://github.com/FiloSottile/mkcert). After installing mkcert, just run:
266 |
267 | ```` bash
268 | mkdir -p /var/www/certs && cd /var/www/certs && mkcert "project.test"
269 | ````
270 |
271 | Then edit your vhost as following (change all from *project* to your project name):
272 |
273 | ```` nginx
274 | server {
275 | listen 443 ssl;
276 | http2 on;
277 | root /var/www/project;
278 | index index.php;
279 | server_name project.test;
280 |
281 | include php.conf;
282 | include global/wordpress.conf;
283 |
284 | ssl_certificate /var/www/certs/project.test.pem;
285 | ssl_certificate_key /var/www/certs/project.test-key.pem;
286 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
287 | ssl_prefer_server_ciphers on;
288 | ssl_dhparam /etc/ssl/certs/dhparam.pem;
289 | ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
290 | ssl_session_timeout 1d;
291 | ssl_session_cache shared:SSL:50m;
292 | ssl_stapling_verify on;
293 | add_header Strict-Transport-Security max-age=15768000;
294 | }
295 |
296 | server {
297 | listen 80;
298 | server_name project.test;
299 | return 301 https://$host$request_uri;
300 | }
301 | ````
302 |
303 | Test with `sudo nginx -t` and if everything is OK, restart nginx.
304 |
305 | ### XDebug
306 |
307 | 1. Check your PHP version with `php --version` and location with `which php`. If the location points to `/usr/bin/php`, you are mistakenly using macOS built-in PHP. Change PHP path to correct location by adding to your ~/.bash_profile file, `sudo nano ~/.bash_profile` (change your PHP version accordingly):
308 | ``` shell
309 | export PATH="$(brew --prefix php@8.3)/bin:$PATH"
310 | ```
311 | 2. Install xdebug
312 |
313 | ```bash
314 | brew install shivammathur/extensions/xdebug@8.3
315 | ```
316 |
317 | ``` shell
318 | $ php --version
319 | PHP 8.3.3 (cli) (built: Feb 13 2024 15:41:14) (NTS)
320 | Copyright (c) The PHP Group
321 | Zend Engine v4.3.3, Copyright (c) Zend Technologies
322 | with Xdebug v3.3.1, Copyright (c) 2002-2023, by Derick Rethans
323 | with Zend OPcache v8.3.3, Copyright (c), by Zend Technologies
324 | ```
325 |
326 | 7. Check where your php.ini file is with `php --ini`
327 | 8. Edit php.ini, for example `sudo nano `
328 | 9. Make sure these are on the first lines:
329 |
330 | ```
331 | xdebug.mode=develop
332 | xdebug.client_port=9003
333 | xdebug.client_host=127.0.0.1
334 | xdebug.remote_handler=dbgp
335 | xdebug.start_with_request=yes
336 | xdebug.discover_client_host=0
337 | xdebug.show_error_trace = 1
338 | xdebug.max_nesting_level=250
339 | xdebug.var_display_max_depth=10
340 | xdebug.log=/var/log/xdebug.log
341 | ```
342 |
343 | 10. Save and close with ctrl + O and ctrl + X
344 | 11. Make sure the log exists `sudo touch /var/log/xdebug.log && sudo chmod 777 /var/log/xdebug.log`
345 | 12. Restart services (requires [Linux-style aliases](#use-linux-style-aliases)) `sudo service php@8.3 restart && sudo service nginx restart`
346 | 13. Install [PHP Debug VSCode plugin](https://marketplace.visualstudio.com/items?itemName=felixfbecker.php-debug)
347 | 14. Add following to launch.json (cmd + + shift + P, "Open launch.json"):
348 |
349 | ``` json
350 | {
351 | "version": "0.2.0",
352 | "configurations": [
353 | {
354 | //"debugServer": 4711, // Uncomment for debugging the adapter
355 | "name": "Listen for Xdebug",
356 | "type": "php",
357 | "request": "launch",
358 | "port": 9003,
359 | "log": true
360 | },
361 | {
362 | //"debugServer": 4711, // Uncomment for debugging the adapter
363 | "name": "Launch",
364 | "request": "launch",
365 | "type": "php",
366 | "program": "${file}",
367 | "cwd": "${workspaceRoot}",
368 | "externalConsole": false
369 | }
370 | ]
371 | }
372 | ```
373 | 15. Xdebug should now work on your editor
374 | 16. PHPCS doesn't need xdebug but will warn about it not working... this causes error in [phpcs-vscode](https://marketplace.visualstudio.com/items?itemName=ikappas.phpcs) because it depends on outputted phpcs json that is not valid with the warning _"Xdebug: [Step Debug] Could not connect to debugging client. Tried: 127.0.0.1:9003 (through xdebug.client_host/xdebug.client_port) :-(_". This can be easily fixed by installing a bash "wrapper":
375 | 17. Rename current phpcs with `sudo mv /usr/local/bin/phpcs /usr/local/bin/phpcs.bak`
376 | 18. Install new with `sudo nano /usr/local/bin/phpcs`:
377 |
378 | ``` bash
379 | #!/bin/bash
380 | XDEBUG_MODE=off /Users/rolle/Projects/phpcs/bin/phpcs "$@"
381 | ```
382 |
383 | 19. Add permissions `sudo chmod +x /usr/local/bin/phpcs`
384 | 20. Make sure VSCode settings.json has this setting:
385 |
386 | ``` json
387 | "phpcs.executablePath": "/usr/local/bin/phpcs",
388 | ```
389 |
390 | ### Redis
391 |
392 | Redis is an open source, in-memory data structure store, used as a database, cache. We are going to install Redis and php-redis.
393 |
394 | Before installation, make sure you do not use PHP provided by macOS. You should be using PHP installed by homebrew. If you are having problems with testing php-redis after installation, it is most probably caused bacuse of using wrong PHP. See [Troubleshooting: Testing which version of PHP you run](#testing-which-version-of-php-you-run) for more information.
395 |
396 | 1. Check that `pecl` command works
397 | 2. Run `brew update` first
398 | 3. Install Redis, `brew install redis`
399 | 4. Start Redis `brew services start redis`, this will also make sure that Redis is always started on reboot
400 | 5. Test if Redis server is running `redis-cli ping`, expected response is `PONG`
401 | 6. Install PHP Redis extension `pecl install redis`.
402 | 7. Restart nginx and php-redis should be available, you can test it with `php -r "if (new Redis() == true){ echo \"\r\n OK \r\n\"; }"` command, expected response is `OK`
403 |
404 | ### Troubleshooting
405 |
406 | #### PHP Warning: PHP Startup: Unable to load dynamic library 'redis.so
407 |
408 | If you get something like this:
409 |
410 | ```shell
411 | PHP Warning: PHP Startup: Unable to load dynamic library 'redis.so' (tried: /opt/homebrew/lib/php/pecl/20190902/redis.so (dlopen(/opt/homebrew/lib/php/pecl/20190902/redis.so, 0x0009): tried: '/opt/homebrew/lib/php/pecl/20190902/redis.so' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/opt/homebrew/lib/php/pecl/20190902/redis.so' (no such file), '/opt/homebrew/lib/php/pecl/20190902/redis.so' (no such file)), /opt/homebrew/lib/php/pecl/20190902/redis.so.so (dlopen(/opt/homebrew/lib/php/pecl/20190902/redis.so.so, 0x0009): tried: '/opt/homebrew/lib/php/pecl/20190902/redis.so.so' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/opt/homebrew/lib/php/pecl/20190902/redis.so.so' (no such file), '/opt/homebrew/lib/php/pecl/20190902/redis.so.so' (no such file))) in Unknown on line 0
412 | ```
413 |
414 | Install [phpredis](https://github.com/phpredis/phpredis) from source:
415 |
416 | ```
417 | git clone https://www.github.com/phpredis/phpredis.git
418 | cd phpredis
419 | phpize && ./configure && make && sudo make install
420 | ```
421 |
422 | Then take copy the outputted library path, it will be something like this: `/opt/homebrew/Cellar/php@8.3/8.3.3/pecl/20190902/`.
423 |
424 | Run `php --ini` and modify your php.ini with `nano -w /path/to/php.ini`.
425 |
426 | Replace `extension="redis.so"` with `extension="/opt/homebrew/Cellar/php@8.3/8.3.3/pecl/20190902/redis.so"` where the path is the one you copied. Restart nginx just in case. After this phpredis should work.
427 |
428 | #### Testing which version of PHP you run
429 |
430 | Test with `php --version` what version of PHP you are using, if the command returns something like `PHP is included in macOS for compatibility with legacy software` and especially when `which php` is showing /usr/bin/php then you are using macOS built-in version (which will be removed in the future anyway) and things most probably won't work as expected.
431 |
432 | To fix this, first find the PHP:
433 |
434 | ```bash
435 | sudo find / -name 'php'
436 | ```
437 |
438 | Look for the bin under Cellar or homebrew dirs. Then run command with your php bin, for example: `sudo ln -s /usr/local/Cellar/php@8.3/8.3.3/bin/php /usr/local/bin/php`. This symlinks the homebrew version to be used instead of macOS version OR use bashrc export as defined [here in step 4](https://github.com/digitoimistodude/macos-lemp-setup#installation).
439 |
440 | #### PHP or mysql not working at all
441 |
442 | If you have something like this in your /var/log/nginx/error.log:
443 |
444 | ```
445 | 2019/08/12 14:09:04 [crit] 639#0: *129 open() "/usr/local/var/run/nginx/client_body_temp/0000000005" failed (13: Permission denied), client: 127.0.0.1, server: project.test, request: "POST /wp/wp-admin/async-upload.php HTTP/1.1", host: "project.test", referrer: "http://project.test/wp/wp-admin/upload.php"
446 | ```
447 |
448 | If you cannot login to mysql from other than localhost, please answer with n to the question Disallow root login remotely? [Y/n]
when running sudo mysql_secure_installation
.
449 |
450 | **Make sure you run nginx and php-fpm on your root user and mariadb on your regular user**. This is important. Stop nginx from running on your default user by `brew services stop nginx` and run it with sudo `sudo brew services start nginx`.
451 |
452 | sudo brew services list
should look like this:
453 |
454 | ``` shell
455 | ~ sudo brew services list
456 | Name Status User Plist
457 | dnsmasq started root /Library/LaunchDaemons/homebrew.mxcl.dnsmasq.plist
458 | mariadb started rolle /Users/rolle/Library/LaunchAgents/homebrew.mxcl.mariadb.plist
459 | nginx started root /Library/LaunchDaemons/homebrew.mxcl.nginx.plist
460 | php started root /Library/LaunchDaemons/homebrew.mxcl.php@8.3.plist
461 | ```
462 |
463 | You may have "unknown" or "error" as status or different PHP version, that is not a problem if ther server runs. **User** should be like in the list above. Then everything should work.
464 |
465 | #### MySQL/MariaDb issues
466 |
467 | If you get problems like:
468 |
469 | ```
470 | ERROR 2002 (HY000): Can't connect to MySQL server on '127.0.0.1' (36)
471 | ```
472 |
473 | It seems you have messed up with your root password. Try resetting root password with by adding this to your home directory (for example /Users/rolle/mysql-init):
474 |
475 | Try resetting root password with (add new password in place of _YOUR_NEW_PASSWORD_):
476 |
477 | ```
478 | ALTER USER 'root'@'localhost' IDENTIFIED BY 'YOUR_NEW_PASSWORD';
479 | ```
480 |
481 | Then kill all mysql processes:
482 |
483 |
484 | ```
485 | sudo ps xa |grep mysql
486 | kill -9
487 | ```
488 |
489 |
490 | Then run:
491 |
492 | ```
493 | mysqld --init-file=/Users/rolle/mysql-init &
494 | ```
495 |
496 | After this:
497 |
498 | ```
499 | sudo mysql_secure_installation
500 | ```
501 |
502 | Answer:
503 |
504 | ```
505 | NOTE: RUNNING ALL PARTS OF THIS SCRIPT IS RECOMMENDED FOR ALL MariaDB
506 | SERVERS IN PRODUCTION USE! PLEASE READ EACH STEP CAREFULLY!
507 |
508 | In order to log into MariaDB to secure it, we'll need the current
509 | password for the root user. If you've just installed MariaDB, and
510 | haven't set the root password yet, you should just press enter here.
511 |
512 | Enter current password for root (enter for none):
513 | OK, successfully used password, moving on...
514 |
515 | Setting the root password or using the unix_socket ensures that nobody
516 | can log into the MariaDB root user without the proper authorisation.
517 |
518 | You already have your root account protected, so you can safely answer 'n'.
519 |
520 | Switch to unix_socket authentication [Y/n] n
521 | ... skipping.
522 |
523 | You already have your root account protected, so you can safely answer 'n'.
524 |
525 | Change the root password? [Y/n] n
526 | ... skipping.
527 |
528 | By default, a MariaDB installation has an anonymous user, allowing anyone
529 | to log into MariaDB without having to have a user account created for
530 | them. This is intended only for testing, and to make the installation
531 | go a bit smoother. You should remove them before moving into a
532 | production environment.
533 |
534 | Remove anonymous users? [Y/n] y
535 | ... Success!
536 |
537 | Normally, root should only be allowed to connect from 'localhost'. This
538 | ensures that someone cannot guess at the root password from the network.
539 |
540 | Disallow root login remotely? [Y/n] n
541 | ... skipping.
542 |
543 | By default, MariaDB comes with a database named 'test' that anyone can
544 | access. This is also intended only for testing, and should be removed
545 | before moving into a production environment.
546 |
547 | Remove test database and access to it? [Y/n] y
548 | - Dropping test database...
549 | ... Success!
550 | - Removing privileges on test database...
551 | ... Success!
552 |
553 | Reloading the privilege tables will ensure that all changes made so far
554 | will take effect immediately.
555 |
556 | Reload privilege tables now? [Y/n] y
557 | ... Success!
558 |
559 | Cleaning up...
560 |
561 | All done! If you've completed all of the above steps, your MariaDB
562 | installation should now be secure.
563 |
564 | Thanks for using MariaDB!
565 | ```
566 |
567 | If you are still having problems connecting with WordPress and prompting `Access denied for user 'root'@'127.0.0.1'`, try this in `mysql -u root -p`:
568 |
569 | ``` sql
570 | GRANT ALL PRIVILEGES ON *.* TO root@localhost IDENTIFIED BY 'YOUR_MYSQL_ROOT_PASSWORD' WITH GRANT OPTION;
571 | GRANT ALL PRIVILEGES ON *.* TO root@127.0.0.1 IDENTIFIED BY 'YOUR_MYSQL_ROOT_PASSWORD' WITH GRANT OPTION;
572 | ```
573 |
574 | #### *2 open() "/var/www/test.txt" failed (13: Permission denied), client: 127.0.0.1
575 |
576 | If you are getting permission denied by nginx, you need to make sure your php-fpm and nginx are running on the same user. This is stricter on MacBook Pro M1.
577 |
578 | Open `/opt/homebrew/etc/php/8.3/php-fpm.d/www.conf` and change the user, group and listen to following:
579 |
580 | ```ini
581 | user = your_username
582 | group = staff
583 | listen = 127.0.0.1:9074
584 | ```
585 |
586 | Open `/opt/homebrew/etc/nginx/nginx.conf` and add to first line:
587 |
588 | ```ini
589 | user your_username staff;
590 | ```
591 |
592 | #### "Primary script unknown" error in nginx log or "File not found." in browser
593 |
594 | This is caused by php-fpm not running properly. Please [make sure the PHP runs on correct permissions](#make-sure-the-php-runs-on-correct-permissions) section.
595 |
--------------------------------------------------------------------------------
/install.sh:
--------------------------------------------------------------------------------
1 | # Crucial paths
2 | HOMEBREW_PATH='/opt/homebrew'
3 | PHP_VERSION='8.3'
4 |
5 | # Helper variables
6 | TXTBOLD=$(tput bold)
7 | BOLDGREEN=${TXTBOLD}$(tput setaf 2)
8 | YELLOW=$(tput setaf 3)
9 | GREEN=$(tput setaf 2)
10 | TXTRESET=$(tput sgr0)
11 |
12 | echo "${YELLOW}Getting dependencies...${TXTRESET}"
13 |
14 | # Attempt to install Xcode Command Line Tools
15 | xcode-select --install
16 |
17 | # Get Homebrew
18 | /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
19 |
20 | # Inspect Homebrew installation
21 | brew doctor
22 |
23 | # Update Homebrew
24 | brew update && brew upgrade
25 | echo "${BOLDGREEN}Dependencies installed and up to date.${TXTRESET}"
26 |
27 | # Install the latest nginx version
28 | echo "${YELLOW}Installing nginx.${TXTRESET}"
29 | brew tap homebrew/nginx
30 | brew install nginx
31 | sudo brew services start nginx
32 | curl -IL http://127.0.0.1:80
33 | echo "${BOLDGREEN}nginx installed and running.${TXTRESET}"
34 |
35 | # Set up nginx and paths
36 | echo "${YELLOW}Setting up nginx...${TXTRESET}"
37 |
38 | # Chec if /etc/nginx exists
39 | if [ -d "/etc/nginx" ]; then
40 | echo "${YELLOW}Directory /etc/nginx exists.${TXTRESET}"
41 | else
42 | echo "${YELLOW}Directory /etc/nginx does not exist. Symlinking homebrew installation to it.${TXTRESET}"
43 | sudo ln -sfnv "${HOMEBREW_PATH}/etc/nginx" "/etc/nginx"
44 |
45 | # Creating the necessary directories
46 | sudo mkdir -p "${HOMEBREW_PATH}/etc/nginx/global"
47 | sudo mkdir -p "${HOMEBREW_PATH}/etc/nginx/sites-enabled"
48 | sudo mkdir -p "${HOMEBREW_PATH}/etc/nginx/sites-available"
49 | sudo mkdir -p "${HOMEBREW_PATH}/etc/nginx/global"
50 |
51 | # Setting permissions
52 | sudo chmod -R 775 "${HOMEBREW_PATH}/etc/nginx/global"
53 | sudo chmod -R 775 "${HOMEBREW_PATH}/etc/nginx/sites-enabled"
54 | sudo chmod -R 775 "${HOMEBREW_PATH}/etc/nginx/sites-available"
55 | sudo chmod -R 775 "${HOMEBREW_PATH}/etc/nginx/global"
56 | fi
57 |
58 | echo "${YELLOW}Creating default nginx configurations...${TXTRESET}"
59 |
60 | # Create default nginx configuration
61 | sudo echo "user $(whoami) staff;
62 |
63 | worker_processes 18;
64 |
65 | events {
66 | multi_accept on;
67 | accept_mutex on;
68 | worker_connections 1024;
69 | }
70 |
71 | http {
72 | ##
73 | # Optimization
74 | ##
75 |
76 | sendfile on;
77 | sendfile_max_chunk 512k;
78 | tcp_nopush on;
79 | tcp_nodelay on;
80 | keepalive_timeout 120;
81 | keepalive_requests 100000;
82 | types_hash_max_size 2048;
83 | server_tokens off;
84 | client_body_buffer_size 128k;
85 | client_max_body_size 10m;
86 | client_header_buffer_size 1k;
87 | large_client_header_buffers 4 32k;
88 | output_buffers 1 32k;
89 | postpone_output 1460;
90 |
91 | server_names_hash_max_size 1024;
92 | #server_names_hash_bucket_size 64;
93 | # server_name_in_redirect off;
94 |
95 | include ${HOMEBREW_PATH}/etc/nginx/mime.types;
96 | default_type application/octet-stream;
97 |
98 | ##
99 | # Logging Settings
100 | ##
101 | access_log off;
102 | access_log /var/log/nginx/access.log combined;
103 | error_log /var/log/nginx/error.log;
104 |
105 | ##
106 | # Virtual Host Configs
107 | ##
108 |
109 | include ${HOMEBREW_PATH}/etc/nginx/sites-enabled/*;
110 | }" > "${HOMEBREW_PATH}/etc/nginx/nginx.conf"
111 |
112 | echo "${YELLOW}Creating locations for logs...${TXTRESET}"
113 |
114 | # Check if nginx log dir exists
115 | if [ -d "/var/log/nginx" ]; then
116 | echo "${YELLOW}Directory /var/log/nginx exists.${TXTRESET}"
117 | else
118 | echo "${YELLOW}Directory /var/log/nginx does not exist. Creating it and the access.log and error.log files.${TXTRESET}"
119 | sudo mkdir -p /var/log/nginx
120 | sudo touch /var/log/nginx/access.log
121 | sudo chmod 777 /var/log/nginx/access.log
122 | sudo touch /var/log/nginx/error.log
123 | sudo chmod 777 /var/log/nginx/error.log
124 | fi
125 |
126 | echo "${YELLOW}Creating default PHP/nginx configurations...${TXTRESET}"
127 |
128 | # Create default php configuration
129 | sudo echo "location ~ \.php\$ {
130 | proxy_intercept_errors on;
131 | try_files \$uri /index.php;
132 | fastcgi_split_path_info ^(.+\.php)(/.+)\$;
133 | include fastcgi_params;
134 | fastcgi_read_timeout 300;
135 | fastcgi_buffer_size 128k;
136 | fastcgi_buffers 8 128k;
137 | fastcgi_busy_buffers_size 128k;
138 | fastcgi_temp_file_write_size 128k;
139 | fastcgi_index index.php;
140 | fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
141 | fastcgi_pass 127.0.0.1:9000;
142 | }" > "${HOMEBREW_PATH}/etc/nginx/php.conf"
143 |
144 | echo "${YELLOW}Creating default WordPress-related nginx configurations...${TXTRESET}"
145 |
146 | # Create default WordPress-related nginx configuration
147 | sudo echo "# WordPress single site rules.
148 | # Designed to be included in any server {} block.
149 | # Upstream to abstract backend connection(s) for php
150 | location = /favicon.ico {
151 | log_not_found off;
152 | access_log off;
153 | }
154 |
155 | location = /robots.txt {
156 | allow all;
157 | log_not_found off;
158 | access_log off;
159 | }
160 |
161 | location / {
162 | # This is cool because no php is touched for static content.
163 | # include the "?\$args" part so non-default permalinks doesn't break when using query string
164 | try_files \$uri \$uri/ /index.php?\$args;
165 | }
166 |
167 | # Add trailing slash to */wp-admin requests.
168 | rewrite /wp-admin\$ \$scheme://\$host\$uri/ permanent;
169 |
170 | # Directives to send expires headers and turn off 404 error logging.
171 | location ~* ^.+\.(ogg|ogv|svg|svgz|eot|otf|woff|mp4|ttf|rss|atom|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|ppt|tar|mid|midi|wav|bmp|rtf)\$ {
172 | access_log off;
173 | log_not_found off;
174 | expires max;
175 | }" > "${HOMEBREW_PATH}/etc/nginx/global/wordpress.conf"
176 |
177 | echo "${YELLOW}Creating default site vhost...${TXTRESET}"
178 |
179 | # Create default site configuration
180 | sudo echo "server {
181 | listen 80 default_server;
182 | root /var/www;
183 | index index.html index.htm index.php;
184 | server_name localhost;
185 | include php.conf;
186 | include global/wordpress.conf;
187 | }" > "${HOMEBREW_PATH}/etc/nginx/sites-available/default"
188 |
189 | echo "${YELLOW}Symlinking the default vhost...${TXTRESET}"
190 |
191 | # Symlink the default site configuration
192 | sudo ln -sfnv ${HOMEBREW_PATH}/etc/nginx/sites-available/default ${HOMEBREW_PATH}/etc/nginx/sites-enabled/default
193 |
194 | echo "${YELLOW}Making sure /var/www exists and is linked to project directory...${TXTRESET}"
195 |
196 | # Check if /var/www exists
197 | if [ -d "/var/www" ]; then
198 | echo "${YELLOW}Directory /var/www exists.${TXTRESET}"
199 | else
200 | echo "${YELLOW}Directory /var/www does not exist. Symlinking $HOME/Projects to it.${TXTRESET}"
201 | sudo ln -sfnv $HOME/Projects /var/www
202 | fi
203 |
204 | echo "${YELLOW}Copying default nginx index to the project dir...${TXTRESET}"
205 |
206 | # Figure out the nginx correct installation path
207 | NGINX_VERSION=$(nginx -v 2>&1 | grep -o '[0-9.]*')
208 | NGINX_HOMEBREW_PATH=$(find $HOMEBREW_PATH -name "nginx" -type d -maxdepth 3)
209 | NGINX_HOMEBREW_VERSION_PATH=$(find $NGINX_HOMEBREW_PATH -name $NGINX_VERSION -type d -maxdepth 1)
210 |
211 | # Copy the default index.html to the nginx root
212 | sudo cp "${NGINX_HOMEBREW_VERSION_PATH}/html/index.html /var/www/"
213 |
214 | echo "${YELLOW}Setting permissions...${TXTRESET}"
215 |
216 | # Set the correct permissions
217 | sudo chown -R $(whoami):staff /var/www
218 |
219 | # Install PHP
220 | echo "${YELLOW}Installing PHP...${TXTRESET}"
221 |
222 | # This part is the same than in here:
223 | # Install and use PHP $PHP_VERSION in your local macos-lemp-setup environment
224 | # https://app.gitbook.com/o/PedExJWZmbCiZe4gDwKC/s/VVikkYgIZ9miBzwYDCYh/how-tos/install-and-use-php-8.3-in-your-local-macos-lemp-setup-environment
225 |
226 | # Add the PHP and PHP Extension taps
227 | brew tap shivammathur/php
228 | brew tap shivammathur/extensions
229 |
230 | # Install the PHP we need
231 | brew install shivammathur/php/php@${PHP_VERSION}
232 |
233 | # Pin package to avoid accidental upgrades later
234 | brew pin php@${PHP_VERSION}
235 |
236 | # Link PHP CLI executable as `php`
237 | brew link --overwrite --force shivammathur/php/php@${PHP_VERSION}
238 |
239 | # Test installation, should display PHP 8.3.x (cli)
240 | php -v
241 |
242 | # Test php-fpm
243 | lsof -Pni4 | grep LISTEN | grep php
244 |
245 | # Symlink the PHP configuration
246 | sudo ln -s $HOMEBREW_PATH/etc/php /etc/php
247 |
248 | # Add PHP path to the PATH
249 | sudo echo "export PATH=\"\$(brew --prefix php@${PHP_VERSION})/bin:\$PATH\"" >> ~/.bash_profile
250 |
251 | # Restart
252 | sudo brew services stop php@${PHP_VERSION}
253 | sudo brew services start php@${PHP_VERSION}
254 | echo "${BOLDGREEN}PHP installed and running.${TXTRESET}"
255 |
256 | # Install MariaDB
257 | echo "${YELLOW}Installing MariaDB...${TXTRESET}"
258 | brew install mariadb
259 | brew services start mariadb
260 | echo "${BOLDGREEN}MariaDB installed and running.${TXTRESET}"
261 |
262 | # Install MailHog
263 | echo "${YELLOW}Installing MailHog...${TXTRESET}"
264 | brew update && brew install mailhog
265 | echo "${BOLDGREEN}MailHog installed (run mailhog to start mail server).${TXTRESET}"
266 |
267 | # Install DNSmasq
268 | echo "${YELLOW}Installing DNSmasq...${TXTRESET}"
269 | brew install dnsmasq
270 | curl -L https://gist.githubusercontent.com/dtomasi/ab76d14338db82ec24a1fc137caff75b/raw/550c84393c4c1eef8a3e68bb720df561b5d3f175/dnsmasq.conf -o /usr/local/etc/dnsmasq.conf
271 | sudo curl -L https://gist.githubusercontent.com/dtomasi/ab76d14338db82ec24a1fc137caff75b/raw/550c84393c4c1eef8a3e68bb720df561b5d3f175/dev -o /etc/resolver/dev
272 | sudo brew services stop dnsmasq
273 | sudo brew services start dnsmasq
274 | echo "${BOLDGREEN}DNSmasq installed and configured.${TXTRESET}"
275 |
276 | # Restart all services
277 | echo "${YELLOW}Restarting services....${TXTRESET}"
278 |
279 | # These need to be running as root, because of the port 80 and other privileges.
280 | sudo brew services stop dnsmasq
281 | sudo brew services start dnsmasq
282 | sudo brew services stop nginx
283 | sudo brew services start nginx
284 | sudo brew services stop php@${PHP_VERSION}
285 | sudo brew services start php@${PHP_VERSION}
286 | brew services stop mariadb
287 | brew services start mariadb
288 | sudo brew services list
289 |
290 | # All done
291 | echo "${BOLDGREEN}You should now be able to use http://localhost. If not, test with commands sudo nginx -t and sudo php-fpm -t and fix errors. Add new vhosts to /opt/homebrew/etc/nginx/sites-available and symlink them just like you would do in production. Have fun!${TXTRESET}"
292 |
--------------------------------------------------------------------------------